Cool C Language Techniques

Click the blue text
Cool C Language Techniques
Follow us

Due to changes in the public account’s push rules, please click “View” and add “Star” to get exciting technical shares as soon as possible

Source from the internet, please delete if infringing

C language often makes people feel that what it can express is 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 simple way to initialize various collection types (such as structs, unions, and arrays).
2. Arrays
We can specify the elements of an array for initialization. This is very useful, especially when we need to keep some mapping relationship synchronized based on a set of #define. 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 for each error code’s description. To ensure the array stays updated with the latest definitions, regardless of any modifications or additions to the header file, we can use this array designated syntax.
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 enough space, ensuring that the maximum index is valid while initializing special indices to specified values and the remaining indices to 0.
3. Structs and Unions
Using the field names of structs and unions to initialize data is very useful. Suppose we define:
struct point {int x; int y; int z;}
Then we initialize the struct point like this:
struct point p = {.x = 3, .y = 4, .z = 5};
This method allows for easy generation of structs at compile time without needing to call a specific initialization function when we do not want to initialize all fields to 0.
For unions, we can use the same method, but we only need to initialize one field.
4. Macro Lists
A common practice in C is to say there is a named entity list for which we need to create functions for each of them, initialize each, and expand their names in different code modules. This is often used in Mozilla’s source code, and I learned this trick there. For example, in the project I worked on last summer, we had a macro list for marking each command. It works like this:
#define FLAG_LIST(_) \
 _(InWorklist) \
 _(EmittedAtUses) \
 _(LoopInvariant) \
 _(Commutative) \
 _(Movable) \
 _(Lowered) \
 _(Guard)
Follow the public account: C Language Chinese Community, to get 300G of programming materials for free
It defines a FLAG_LIST macro, which has a parameter called _, which itself is a macro that can call each parameter in the list. A practical example may illustrate the issue more intuitively. 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};
Next, 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};
Then we may need to define some accessor functions to better use 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. If there are still questions about its use, you can spend some time on gcc -E.
5. Compile-Time Assertions
This is actually a very “creative” feature implemented using macros in C language. Sometimes, especially in kernel programming, being able to perform assertions conditionally at compile time rather than at runtime is very useful. 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). Various methods can achieve this, usually by creating an array or struct of negative size. The most common method 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 (condition) evaluates to a non-zero value (i.e., true in C), meaning that ! (condition) is zero, then the code will compile smoothly and generate a structure of size zero. If (condition) results in 0 (which is false in C), then a compilation error will occur when trying to generate a structure of negative size.
Its usage is very simple; if any hypothetical condition can be checked statically, then it can assert at compile time. For example, in the flag list mentioned above, 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 assigned, then the compilation will fail.

If you are over 18 years old and find learning [C language] too difficult? Want to try other programming languages? Then I recommend you learn Python. Currently, a Python zero-based course worth 499 yuan is available for free, limited to 10 places!



▲ Scan the QR code - Get it for free


Cool C Language Techniques
Click to read the original text to learn more

Leave a Comment