Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
341 lines (247 sloc) 10.4 KB

C programming language

See also:

Lack of namespaces

  • Common prefix for identifiers to prevent conflicts:
    • glib: g_
    • Gtk: gtk_
    • Python: Py_, _Py_ ("private") or PY_ (define)
  • static keyword
    • function only accessible from the current file, not exported
    • variable only accessible from the current file, not exported
    • variable local to a function
  • #include: C preprocessor

Python has the make smelly tool to detect exported symbols which doesn't start with Py_ nor _Py_.

Issues with types

  • comparison between signed and unsigned: who wins? cast signed to unsigned, or cast unsigned to signed? => obvious integer overflow
  • integer overflow: undefined behaviour, the compiler is free to optimize. Check if an operation will overflow is hard, especially for signed numbers. GCC added builtin functions to check that in a portable and efficient way.
  • portability: int==long==void*, no warning. if int and long have the same size, downcasting a long into an int is allowed, and don't emit any warning.
  • int stored in void*
  • void* vs intptr_t vs uintptr_t
  • Use "int" to access an item of an array in a loop: the compiler may have to handle integer overflow. size_t or ssize_t preferred here?

Portability

  • Perl still uses C89
  • Python only started to move to C99 with Python 3.6, only CPython is restricted to a subset of C99, the most portable parts of C99...
  • GNU extensions of GCC
  • glibc vs all other C libraries (ex: musl libc and uclibc). GNU extensions, again. Implementation bugs. Errno is sometimes set to 0 on success, sometimes it is not set on failure, it depends on the called function and the libc.
  • <stdint.h> not fully supported in 2017, need some hacks to support old C compilers
  • <stdatomic.h> is still "new" and not well supported by C compilers
  • <stdbool.h>?

Issues with C++

  • C code used in C++ has to be careful with exceptions: need to compile C with -fexceptions?
  • extern { trickery for header files

Undefined Behaviour

Strict Aliasing

Magic UNION_CAST() macro:

#define UNION_CAST(x, destType) \
   (((union {__typeof__(x) a; destType b;})x).b)

C aliasing

Change which fixed a crash after the merged of the new dict implementation on a specific platform (don't recall which one!): https://github.com/python/cpython/commit/186122ead26f3ae4c2bc9f6715d2a29d339fdc5a

Example:

#include <stdint.h>
#include <stdio.h>

uint32_t
swap_words( uint32_t arg )
{
  uint16_t* const volatile sp = (uint16_t*)&arg;
  uint16_t        hi = sp[0];
  uint16_t        lo = sp[1];

  sp[1] = hi;
  sp[0] = lo;

  return (arg);
}

int main(void)
{
    uint32_t x = 0xabcd1234;
    uint32_t y = swap_words(x);
    printf("x=%lx\n", (long unsigned int)x);
    printf("y=%lx\n", (long unsigned int)y);
    return 0;
}

Bug:

$ LANG= gcc -O3 x.c -o x -fstrict-aliasing -Wstrict-aliasing=2 && ./x
x.c: In function 'swap_words':
x.c:7:3: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
   uint16_t* const volatile sp = (uint16_t*)&arg;
   ^~~~~~~~
x=abcd1234
y=abcd1234

volatile

volatile is discouraged in the Linux kernel in favor of smaller locks: https://github.com/torvalds/linux/blob/master/Documentation/process/volatile-considered-harmful.rst

GCC warnings

  • -Wall: some warnings
  • -Wall -Wextra: more warnings
  • -Wall -Wextra -O3: even more warnings. Some warnings are only emitted when the compiler optimizes the code, like dead code or unused variables.
  • There are even more. GCC is able to emit even more warnings, but they must be enabled explictly!
    • -fstrict-aliasing -Wstrict-aliasing=2

Platforms #define

  • AIX: #ifdef _AIX
  • FreeBSD: #ifdef __FreeBSD__
  • HP-UX: #ifdef __hpux
  • Linux: #ifdef __linux__
  • NetBSD: #ifdef __NetBSD__
  • Solaris: #ifdef sun
  • Windows: _WIN32 or _WIN64
  • macOS: #ifdef __APPLE__

Compiler defines

GCC flags

https://developers.redhat.com/blog/2018/03/21/compiler-and-linker-flags-gcc/

Compile in 32-bit mode on Fedora

  • dnf install glibc-devel.i686
  • gcc -m32

Example:

$ echo 'int main() { return sizeof(void *); }' > x.c
$ gcc x.c -o x -m32 && ./x; echo $?
4

Configure in 32-bit:

./configure CFLAGS="-m32" LDFLAGS="-m32" && make

For Python, install also libffi, openssl and zlib:

dnf install -y libffi-devel.i686 openssl-devel.i686 zlib-devel.i686

Compiler and linker options

C macros (preprocessor)

  • typeof(expr): C99
  • offsetof(type, member): <stddef.h>, C89
  • _builtin_types_compatible_p(type1, type2): true if type1 is type2; GCC and clang.

Magic BUILD_ASSERT_EXPR() macro by Rusty Russell:

#define BUILD_ASSERT_EXPR(cond) \
    (sizeof(char [1 - 2*!(cond)]) - 1)

Magic ARRAY_LENGTH() macro by Rusty Russell, compilation error with GCC if the argument is not an array but a pointer:

#if (defined(__GNUC__) && !defined(__STRICT_ANSI__) && \
    (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) || (__GNUC__ >= 4)))
/* Two gcc extensions.
   &a[0] degrades to a pointer: a different type from an array */
#define ARRAY_LENGTH(array) \
    (sizeof(array) / sizeof((array)[0]) \
     + BUILD_ASSERT_EXPR(!__builtin_types_compatible_p(typeof(array), \
                                                       typeof(&(array)[0]))))
#else
#define ARRAY_LENGTH(array) \
    (sizeof(array) / sizeof((array)[0]))
#endif

Is a type signed or unsigned?

#define IS_TYPE_UNSIGNED(type) (((type)0 - 1) > 0)

Convert to a string

STRINGIFY(expr) macro:

#define _XSTRINGIFY(x) #x

/* Convert the argument to a string. For example, STRINGIFY(123) is replaced
   with "123" by the preprocessor. Defines are also replaced by their value.
   For example STRINGIFY(__LINE__) is replaced by the line number, not
   by "__LINE__". */
#define STRINGIFY(x) _XSTRINGIFY(x)

<sys/cdefs.h> defines two macros:

#define __CONCAT(x,y) x ## y
#define __STRING(x) #x

But __CONCAT and __STRING are not portable. For example, NetBSD says "only works with ANSI C". Comment on Linux: "For these things, GCC behaves the ANSI way normally, and the non-ANSI way under -traditional."

const

General:

  • const int *x is the same than int const *x: it only matters if const if before or after *

Single *, constant x, but *x is mutable:

int * const x = (int * const)1;
x = (int * const)2; /* compilation error */
*x = 3; /* ok */

Single *, constant *x, but x is mutable:

const int *x = (const int *)1;
x = (const int *)2; /* ok */
*x = 3; /* compilation error */

Single *, constant x and constant *x:

const int * const x = (const int * const)1;
x = (const int * const)2; /* compilation error */
*x = 3;  /* compilation error */

Problem of casting char ** to const char **: c-faq.com/ansi/constmismatch.html

C FAQ

http://c-faq.com/

You can’t perform that action at this time.