There are many C features which are lesser known even to experienced programmers.
Do not be confused with dynamic arrays. C99 supports arrays of variable length where the size is calculated at run-time while processing the array definition. This is a back-port from C++ and far better than the alloca
function. Have a look at the example below.
int fo( int i) { return i+1; } void f( int len, char s[ len][ fo( len+1)*2]) { for (int i=1; i<10; i++) { char s1[ i+len]; ... } }
It is not possible to skip the definition using break in switch statement.
Arrays with unspecified size are used in structures to help addressing data past the structure end.
struct s { int i; unsigned u; char end[]; };
There are several restrictions. The flexible array member must be the last element of the structure and the structure itself must be neither used in another structure definition nor as a member of an array.
In old C the fields of a structure have to be in fixed order during initialization. It is a well-known GNU extension that specifies structure fields with the label-like syntax. C99 has a new approach using the . operator. Although it looks strange, it has a hidden meaning (see below).
struct fops { int open, read, write, close; }; { // old span struct fops f1 = { open: 0, close: 1, read: 2}; // new span struct fops f2 = { .open=0, .close=1, .read=2} }; }
It is not very well known that an array initiator may jump in index like in the enumeration definition.
// initializing an array of int int a[ 7] = { [5]=1, [2]=3, 2}; // resulting in int a[ 7] = { 0, 0, 3, 2, 0, 1, 0}; // initializing an array of struct struct { int x,y; } ar[ 4] = { [1].x=23, [3].y=34, [1].y=-1, [1].x=12}; // resulting in struct { int x,y; } ar[ 4] = { { 0, 0}, { 12, -1}, { 0, 0}, { 0, 34}}; // interesting usage char forbidden[ 256] = { ['a']=1, ['e']=1, ['i']=1, ['o']=1, ['u']=1};
Note that already initialized fields may be overwritten.
Compounds literals bring a new method of assigning to structures and passing structures as parameters.
struct point { int x, y; }; void foo( struct point p1, struct point p2); { struct point p1 = { 2, 4}; // this is standard p1 = (struct point){ 1, 3}; // this is new // passing to the function foo( (struct point){ 10, 11}, (struct point){ 1, 2}); // constructing an array char **sx = (char *[]){ "Adam", "Eva", "Simon"}; }
Yes, C has inline functions. Like in C++ prepend the function header with the inline
keyword (__inline__
in headers). Note that certain constructions may disallow the compiler to inline the function. Especially constructions implemented on stack like variadic parameters, alloca or variable sized arrays.
Have a look at the example below - there are two functions each executed in a separate thread with the same pointer as a parameter. Do you think that the check
function must terminate?
void add( int *i) { while (1) { *i = 0; for (int a=0; a<10; a++) (*i)++; } } void check( int *i) { while (*i == 5) ; }
No, it doesn't have to. The incrementation may by optimized. Below is a disassembled code produced by GCC -O1:
<add+00> push ebp <add+01> mov ebp,esp <add+03> mov ecx,DWORD PTR [ebp+8] <add+06> mov DWORD PTR [ecx],0x0 <add+12> mov eax,0x0 <add+17> mov edx,0x0 <add+22> inc edx <add+23> inc eax <add+24> cmp eax,0x9 <add+27> jle <add+22> <add+29> mov DWORD PTR [ecx],edx <add+31> jmp <add+6>
As we can see, *i
has been replaced by the edx register, which is written only at the beginning and at the end of the cycle. The value of *i
switches between 0 and 10, and never becomes 5.
Marking the pointer as volatile
will solve the problem. The semantics is that a volatile object may change its value outside the scope of local execution and thus every read and write access has to be processed immediately without any optimizations. Focus on this problem when you observe different behavior across different optimization levels. It is not guaranteed that a read or write access even on volatile objects are atomic.
The freedom of pointers in C leads sometimes to slower code. The programmer can enable several optimizations by guarantying that objects referred by pointers do not overlap.
void copy1( char *s1, char *s2, int n) { while (n--) *s1++ = *s2++; } void copy2( char restrict *s1, char restrict *s2, int n) { while (n--) *s1++ = *s2++; }
Both functions copy one block of chars into another. In copy2
the compiler may use word addressing instructions to speedup the execution. But when blocks overlap the behavior is undefined.
The syntax is similar to functions. Parameters in ... are then addressed as __VA_ARGS__
(variable argument).
#define myfunc( A, B, ...) do_something( 0, B, A, __VA_ARGS__);
__FILE__
expands to a string name of a current source file__func__
expands to a string name of a current function__LINE__
is expanded to the line number. This can be overwritten with the #line
preprocessor directive__DATE__
date of compilation__TIME__
time of compilation__STDC_VERSION__
is expanded to long int which represents the ISO norm (199901L as an example)This may looks like a joke, but it is not. All occurrences of sequences ??<, ??>, ??(, ??), ??=, ??/, ??!, ??', ??- in a source file are converted to one of characters { } [ ] # / | ^ ~. So don't be surprised...
As an aside, tokens <: :> <% %> %: behave as [ ] { } # and ## (but the conversion is not performed in strings).
Have a look at the header file stdint.h
. There are typedefined types like (where N is in { 8, 16, 32, 64})
intN_t
- signed integers with exactly specified widthint_leastN_t
- signed integers with a width of at least Nint_fastestN_t
- fastest signed integers with a width of at least NUnsigned variants have prefix "u".
Be careful when using printf
functions. Since you do not know which C type are behind these typedefs, you have to use predefined constants from inttypes.h
. Constants starts with PRI
followed by type character (one of diouxX
), modificator LEAST
or FAST
or nothing, and number of bits. For example, 32 bit fast integer would take the form PRIdFAST32
.
C still does not have a boolean type, but reserves an integer type _Bool
big enough to store 0 and 1. The header file stdbool.h
only typedefines _Bool
as a bool
and defines constants false
as 0 and true
as 1.
This is not C++ bool
!
C has three complex types: {float
, double
, long double
} _Complex
. In the header file complex.h
_Complex
is typedefined to complex
. Complex types may be not implemented in freestanding (without OS) implementations.