The C language often makes people feel that its capabilities are very limited. It does not have advanced features like first-class functions and pattern matching. However, C is very simple, and there are still some very useful syntax tricks and features that not many people know about.
1 Designated Initialization
Many people know how to statically initialize an array like this:
int fibs[] = {1, 1, 2, 3, 5};
The C99 standard actually supports a more intuitive and straightforward way to initialize various collection types (such as structures, unions, and arrays).2 Arrays We can specify elements of an array for initialization. This is very useful, especially when we need to keep a mapping relationship synchronized based on a set of #define directives. Let’s look at a set of error code definitions, such as:
/* Entries may not correspond to actual numbers. Some entries omitted. */
#define EINVAL 1
#define ENOMEM 2
#define EFAULT 3
/* ... */
#define E2BIG 7
#define EBUSY 8
/* ... */
#define ECHILD 12
/* ... */
Now, suppose we want to provide a string description for each error code. To ensure the array stays updated with the latest definitions, regardless of any modifications or additions in the header file, we can use this specified syntax for the array.
char *err_strings[] = {[0] = "Success", [EINVAL] = "Invalid argument", [ENOMEM] = "Not enough memory", [EFAULT] = "Bad address", /* ... */ [E2BIG] = "Argument list too long", [EBUSY] = "Device or resource busy", /* ... */ [ECHILD] = "No child processes" /* ... */};
This allows for static allocation of sufficient space, ensuring that the maximum index is valid while initializing special indices to specified values and the remaining indices to 0.3 Structures and Unions Initializing data using the field names of structures and unions is very useful. Suppose we define:
struct point { int x; int y; int z;}
Then, we can initialize the struct point like this:
struct point p = {.x = 3, .y = 4, .z = 5};
This approach allows for easy generation of the structure at compile time without needing to call a dedicated initialization function when we do not want to initialize all fields to 0. For unions, we can use the same method, but we only initialize one field.4 Macro Lists A common idiom in C is to have a named list of entities for which functions need to be created, initializing each of them and expanding their names in different code modules. This is often used in Mozilla’s source code, and I learned this trick there. For example, in a project I worked on last summer, we had a macro list for flags for each command. It works as follows:
#define FLAG_LIST(_) \
_(InWorklist) \
_(EmittedAtUses) \
_(LoopInvariant) \
_(Commutative) \
_(Movable) \
_(Lowered) \
_(Guard)
It defines a FLAG_LIST macro that takes a parameter called _, which itself is a macro that can call each parameter in the list. A practical example might clarify this. Suppose we define a macro DEFINE_FLAG, like:
#define DEFINE_FLAG(flag) flag,
enum Flag { None = 0, FLAG_LIST(DEFINE_FLAG) Total};
#undef DEFINE_FLAG
Expanding FLAG_LIST(DEFINE_FLAG) yields the following code:
enum Flag { None = 0, DEFINE_FLAG(InWorklist) DEFINE_FLAG(EmittedAtUses) DEFINE_FLAG(LoopInvariant) DEFINE_FLAG(Commutative) DEFINE_FLAG(Movable) DEFINE_FLAG(Lowered) DEFINE_FLAG(Guard) Total};
Then, expanding each parameter with the DEFINE_FLAG macro gives us the following enum:
enum Flag { None = 0, InWorklist, EmittedAtUses, LoopInvariant, Commutative, Movable, Lowered, Guard, Total};
Next, we might want to define some accessor functions to better utilize the flag list:
#define FLAG_ACCESSOR(flag) \
bool is##flag() const {\
return hasFlags(1 << flag);\
}\
void set##flag() {\
JS_ASSERT(!hasFlags(1 << flag));\
setFlags(1 << flag);\
}\
void setNot##flag() {\
JS_ASSERT(hasFlags(1 << flag));\
removeFlags(1 << flag);\
}FLAG_LIST(FLAG_ACCESSOR)
#undef FLAG_ACCESSOR
Step-by-step demonstration of the process is very enlightening, and if there are any uncertainties about its usage, one can spend some time on gcc -E.5 Compile-Time Assertions This is actually a very “creative” feature implemented using macros in C. Sometimes, especially in kernel programming, it is very useful to perform assertions at compile time rather than at runtime. Unfortunately, the C99 standard does not support any compile-time assertions. However, we can use preprocessing to generate code that will only compile if certain conditions are met (preferably commands that do not perform actual functions). There are various ways to achieve this, typically by creating an array or structure of negative size. The most common way is as follows:
/* Force a compilation error if condition is false, but also produce a result* (of value 0 and type size_t), so it can be used e.g. in a structure* initializer (or wherever else comma expressions aren't permitted). *//* Linux calls these BUILD_BUG_ON_ZERO/_NULL, which is rather misleading. */
#define STATIC_ZERO_ASSERT(condition) (sizeof(struct { int:-!(condition); }) )
#define STATIC_NULL_ASSERT(condition) ((void *)STATIC_ZERO_ASSERT(condition) )
/* Force a compilation error if condition is false */
#define STATIC_ASSERT(condition) ((void)STATIC_ZERO_ASSERT(condition))
If the result of (condition) evaluates to a non-zero value (i.e., true in C), then ! (condition) is zero, and the code will compile successfully, generating a zero-sized structure. If the result of (condition) is 0 (false in C), then attempting to generate a structure of negative size will produce a compilation error. Its usage is very simple; if any hypothetical condition can be checked statically, then it can assert at compile time. For example, in the previously mentioned flag list, the type of the flag set is uint32_t, so we can make the following assertion:
STATIC_ASSERT(Total <= 32)
It expands to:
(void)sizeof(struct { int:-!(Total <= 32) })
Now, suppose Total <= 32. Then -!(Total <= 32) equals 0, so this line of code is equivalent to:
(void)sizeof(struct { int: 0 })
This is valid C code. Now suppose there are more than 32 flags, then -!(Total <= 32) equals -1, so this code becomes:
(void)sizeof(struct { int: -1 } )
Since the bit width is negative, it can be determined that if the number of flags exceeds the space we have allocated, the compilation will fail.