What are the unique usages of "##" in C language?

Transferred from: Micro reading   https://www.weidianyuedu.com

There are many popular programming languages on the market, such as Python , JAVA, Go, etc. you may think C language is very old and backward. If you have this idea, you may just be a beginner. Before I shared with you the definition and usage of several special standards in C language, I forgot an important content in C language, which is the usage of ##. Maybe most C programmers don't know the hidden usage of "##". Let's talk about it.

1, ##'s "table" usage

Many people must know the usage of "##" - it is essentially a "glue operation" (the function of connecting strings), which is used to glue the "formal parameters" in the parameter macro with other contents without natural segmentation, such as:

#define def_u32_array(__name, __size)     uint32_t array_##__name[__size];

In practice, we can use:

def_u32_array(sample_buffer, 64)

The effect of macro expansion is:

uint32_t array_sample_buffer[64];

You can see, "array __" There is no natural separation between "array name" and the formal parameter "name", so you want to set "array" If it is attached to the content represented by "_name" (rather than _name itself), it needs the help of "##" operation.

on the other hand, "##" and "[" are naturally separated - the compiler does not think that "##" and "[" are connected, so there is no need to use "##" operation here - if you do, the precompiler will not hesitate to tell you the syntax error. - this is the common use of "##" operation, which has been reprinted in the past The application of C language # and ## connector in the project (beautiful) is also introduced in detail, which will not be repeated here.

2, ##'s official usage of "Li"

"##" also has a little-known usage of "Li". Before introducing it, I have to talk about another parameter macro extension introduced by ANSI-C99 standard - variable parameter macro. for instance:

#define safe_atom_code(...)                          \        {                                            \            uint32_t int_flag = __disable_irq();     \            __VA_ARGS__                              \            __set_PRIMASK(int_flag);                 \        }

A macro "safe_atom_code()" is defined here. In parentheses, whatever you fill in will be unconditionally placed in "_VA_ARGS_" You can think of "..." in parentheses In fact, it corresponds to "_VA_ARGS_". For example, we can write such code:

/**  \fn          void wr_dat (uint16_t dat)  \brief       Write data to the LCD controller  \param[in]   dat  Data to write*/static __inline void wr_dat (uint_fast16_t dat) {    safe_atom_code(        LCD_CS(0);        GLCD_PORT->DAT = (dat >>   8);   /* Write D8..D15 */        GLCD_PORT->DAT = (dat & 0xFF);   /* Write D0..D7 */        LCD_CS(1);    )}

This code ensures that gcld is added to the register_ Port - > dat when writing data, it will not be interrupted by other interrupts.

Smart you may ask such a question soon. What's the difference between the above macro and the following?

#define safe_atom_code(__CODE)                       \        {                                            \            uint32_t int_flag = __disable_irq();     \            __CODE                                   \            __set_PRIMASK(int_flag);                 \        }

You not only asked questions, but even actually tested them. It seems to be completely equivalent, "there's no difference at all!"—— You exclaimed. However, in fact, it is not that simple:

1. The parameter macro uses "," as the separator to calculate several parameters actually passed in by the user, or in other words, when using the parameter macro, the precompiler cannot understand the C syntax - in its eyes, except for a few symbols it knows, everything else is a meaningless string - because when dealing with the content inside the parentheses, It only knows "," and "...", Therefore, when a "," is added to the contents in parentheses, the compiler considers that there is one more parameter.

2. When you use a parameter macro, the number of incoming parameters (separated by ",") must be exactly the same as the number of formal parameters when defining a parameter macro; In case of inconsistency, the precompiler may not report an error, but directly ignore your parameter macro - pass it to the next stage of compilation, so it is often regarded as a function - in fact, this function does not exist, so an undefined error of a function will be reported in the linking stage. At this time, you will wonder why I clearly define a macro, but the compiler regards it as a function?

The introduction of variable parameter macros solves this problem:

  • "..." It can only be placed at the end of the parameter macro formal parameter list;

  • When the number of user parameters exceeds the specified number of parameters, all the extra contents will be changed by "__VA_ARGS__" Carried by;

  • When the number of user parameters is exactly equal to the number of formal parameters, "_VA_ARGS_" Is equivalent to an empty string

Look back at the previous question:

#define safe_atom_code(...)

And

#define safe_atom_code(__CODE)

The difference is that the former can put almost any content including "," in parentheses; The latter cannot tolerate the existence of commas at all - for example, if you call a function and the parameters of the function need to be separated, right? For another example, you use a comma expression... - it's sour to think about it.

In fact, one of the reasons why the variable parameter list was originally born is to solve the problem of using it with the variable parameters (va_args) of C function, for example:

#define log_info(__STRING, ...)    printf(__STRING, __VA_ARGS__)

Therefore, when using, we can write as follows:

log_info("------------------------------------\r\n");log_info(" Cycle Count : %d", total_cycle_cnt);

After macro expansion, it actually corresponds to:

printf("------------------------------------\r\n",);printf(" Cycle Count : %d", total_cycle_cnt);

There seems to be no problem. Have you noticed a detail? A "," is added at the end of the first printf(). Although some compilers, such as GCC, don't care (maybe it's a warning), for Virgo programmers with serious cleanliness, how can this be tolerated? Therefore, when the ANSI-C99 standard introduces variable parameter macros, a less prominent syntax is added: when the following combination appears ", ##_VA_ARGS_", If__ VA_ ARGS__ If it is an empty string, the preceding "," will be deleted together. Therefore, the above macro can be rewritten as:

#define log_info(__STRING, ...)    printf(__STRING,##__VA_ARGS__)

At this point, the previous code will be expanded to:

printf("------------------------------------\r\n");printf(" Cycle Count : %d", total_cycle_cnt);

Virgo says you can sleep at ease this time.

If this is the hidden usage of "##" that 99% of C programmers don't know, I'm sorry for the audience. In fact, the main film of this article has just begun.

3, ##'s operation

Comma expressions, which have always been concerned with the official account, are familiar to friends who have been reprinted before. [C advanced] I heard that "comma expression" is only for show skills. It has been said in great detail, so I won't repeat it here. Simply put, in a comma expression, the rightmost part of the comma will be the real return value of the expression.  

Combined with the previous about ", ###_uva_args_" Do you realize that the comma here can not only be the separator of the parameter list, but also the operator of the comma expression. Combine__ VA_ARGS__ We can write macros like this:

#define EXAMPLE(...)     ( Default value ,##__VA_ARGS__)

It can be used in two ways:

1. When we use parameter macros, we don't fill in any content in parentheses, which will eventually expand to the case of only default values:

EXAMPLE();

Expanded to:

( Default value )

2. When we provide any valid value, it will be expanded into a comma expression:

EXAMPLE(Value we provide);

Expanded to:

( Default value, Value we provide )

According to the characteristics of comma expression, the default value will be discarded at this time (some compilers will report the warning of invalid expression, which is normal, because the compiler notices that the expression represented by "default value" is actually discarded, and it feels that we have written a useless expression).

In fact, this technique is particularly effective for API encapsulation: it allows us to simplify the use of function APIs. For example, when the user ignores, it automatically fills in some default values for the function, and replaces those default values when the user actively provides parameters. Here are two real examples:

(1) Provide default parameters for functions

Suppose we have an initialization function that allows users to configure some parameters through the structure:

typedef struct xxxx_cfg_t {    ...} xxxx_cfg_t;int xxxx_init(xxxx_cfg_t *cfg_ptr);

In order to simplify the user's configuration process, the initialization function will check the pointer CFG_ Whether PTR is NULL. If it is NULL, the default configuration will be used automatically. Otherwise, the user-defined configuration will be used. At this time, we can provide the default value NULL through the macro:

#define XXXX_INIT(...)    xxxx_init((NULL,##__VA_ARGS__))

(2) Provides default mask configuration for message processing

Some message processing functions can process certain types of messages in batches, and the specific message categories selected are usually represented by binary masks, for example:

typedef struct msg_t msg_t;struct {    uint16_t msg;    uint16_t mask;    int (*handler)(msg_t *msg_ptr);} msg_t;

At this point, we can use macros to build a set of syntax sugar:

#define def_msg_map(__name, ...)                            \    const msg_t __name[] = {__VA_ARGS__};    #define add_msg(__msg, __handler, ...)                      \    {                                                       \        .msg = (__msg),                                     \        .handler = &(__handler),                            \        .msk = (0xFFFF, ##__VA_ARGS__),                     \    }

Through macro add_msg we noticed that when the user deliberately omits to set msk, we give the default value 0xFFFF - which probably means that when processing messages, messages must be strictly matched before they can be handed over to the corresponding processing function; When the user specifies msk, it may mean that a certain type of message is handed over to the same message processing function for processing. For example:

/*! \note The high byte indicates the type of operation: for example, 0x00 indicates the control class, 0x01 indicates WRITE, and 0x02 indicates READ */enum {    SIGN_UP      = 0x0001,    WRITE_MEM    = 0x0100,    WRITE_SRAM   = 0x0101,    WRITE_FLASH  = 0x0102,    WRITE_EEPROM = 0x0103,        READ_MEM     = 0x0200,    READ_SRAM    = 0x0201,    READ_FLASH   = 0x0202,    READ_EEPROM  = 0x0203,};extern int iap_sign_up_handler(msg_t *msg_ptr);extern int iap_write_mem(msg_t *msg_ptr);extern int iap_read_mem(msg_t *msg_ptr);def_msg_map( iap_message_map    /* Strict SIGN_UP maps to the corresponding processing function */    add_msg( SIGN_UP,   iap_sign_up_handler ),    /* All WRITE operations are processed in batch, and the mask is used for filtering*/    add_msg( WRITE_MEM, iap_write_mem,       0xFF00 ),     /* All READ operations are processed in batch, and the mask is used for filtering */    add_msg( READ_MEM,  iap_read_mem,        0xFF00 ),)

4, Conclusion

Macros are not the devil that hinders code development and readability, but arrogance about their unfamiliar knowledge.

Keywords: C Programming Back-end

Added by Tea_J on Tue, 01 Mar 2022 02:01:18 +0200