macrosc-preprocessorc99compound-literals

Weird macros for defining constants


I stumbled on the following stuff in someone's source code in C:

typedef struct {
   u32 reg;
} reg_t;

#define _REG(r) ((const reg_t){.reg=(r)})

#define REG_A _REG(123)
#define REG_B _REG(456)
...

and it makes me wonder: what might be the purpose of such sorcery? Couldn't just a simple numerical constant be defined instead? Why do they define a structure, with just one member, and then use C99's compound literals to initialize it? Is there some obscure benefit that I'm missing here that requires such syntax?

(At first I was puzzled by that syntax as well, until I found out that it's a C99 extension. I knew about the "designated initializers", but I didn't know that they can be used this way, with something that looks like a type cast, but from what I understood, it's just C99's way of doing something akin to C++'s constructors. Still, I don't know why do they use this trick here in particular, and what would happen if they didn't.)


Solution

  • what might be the purpose of such sorcery?

    The purpose is to make _REG have type const reg_t.

    Couldn't just a simple numerical constant be defined instead?

    Anything is possible. Most probably, you could rewrite the code.

    Why do they define a structure, with just one member, and then use C99's compound literals to initialize it?

    To make the type of _REG be const reg_t. For type checking.

    it's a C99 extension

    An "extension" in C language, is an extra part, outside the standard. Specific compilers have specific "extensions", which "extend" the basic language. Like GCC compiler has __attribute__() function attributes syntax, which other compilers do not.

    Compound literals are not extra. They are internal, inside C99 standard. All C99 compilers have compound literals.

    Simple type checking example:

    uint8_t reg_get(const reg_t ret);
    int main() {
       reg_get(REG_A); // OK
       reg_get(123); // error
    }
    

    Note: with the code shown, there is no reason for them to be #define. I would do static const reg_t REG_A = {123};. Unless they are used in #ifdef.

    Note: identifiers starting with underscore and upper case letters are reserved in C99. Use code is not allowed to use them.

    Note: prefer to use standard uint32_t from stdint.h instead of user defined types.