Confusing MACRO and enum definition

后端 未结 2 1675
我在风中等你
我在风中等你 2021-01-21 03:40

I was browsing some Route netlink source code.

I wanted to figure out what was the value of RTNLGRP_NEIGH

Source: http://lxr.free-electrons.com/source/include/li

相关标签:
2条回答
  • 2021-01-21 03:54

    The value of RTNLGRP_NEIGH will be 3. You can easily test this with the following program.

    #include <stdio.h>
    
    /* RTnetlink multicast groups */
    enum rtnetlink_groups {
            RTNLGRP_NONE,
    #define RTNLGRP_NONE            RTNLGRP_NONE
            RTNLGRP_LINK,
    #define RTNLGRP_LINK            RTNLGRP_LINK
            RTNLGRP_NOTIFY,
    #define RTNLGRP_NOTIFY          RTNLGRP_NOTIFY
            RTNLGRP_NEIGH,
    #define RTNLGRP_NEIGH           RTNLGRP_NEIGH
            RTNLGRP_TC,
    #define RTNLGRP_TC              RTNLGRP_TC
            RTNLGRP_IPV4_IFADDR,
    #define RTNLGRP_IPV4_IFADDR     RTNLGRP_IPV4_IFADDR
            /* ... */
    #define RTNLGRP_PHONET_IFADDR   RTNLGRP_PHONET_IFADDR
            RTNLGRP_PHONET_ROUTE,
    #define RTNLGRP_PHONET_ROUTE    RTNLGRP_PHONET_ROUTE
            __RTNLGRP_MAX
    };
    #define RTNLGRP_MAX     (__RTNLGRP_MAX - 1)
    
    int
    main()
    {
      printf("RTNLGRP_NEIGH = %d\n", RTNLGRP_NEIGH);
    }
    

    It outputs this:

    RTNLGRP_NEIGH = 3
    

    Since each macro is #defined to its own name, the RTNLGRP_NEIGH in main will be replaced by RTNLGRP_NEIGH. But since the expansion is not recursive, it will stop at this point and the program use the enum constant RTNLGRP_NEIGH which is the fourth and therefore has value 3.

    If you are not sure what the preprocessor does, you can always compile with the -E switch and look at the pre-processed output. Compiling the above example with gcc -E gives (not showing 840 lines of the #included standard library headers)

    # 4 "main.c"
    enum rtnetlink_groups {
            RTNLGRP_NONE,
    
            RTNLGRP_LINK,
    
            RTNLGRP_NOTIFY,
    
            RTNLGRP_NEIGH,
    
            RTNLGRP_TC,
    
            RTNLGRP_IPV4_IFADDR,
    
    
    
            RTNLGRP_PHONET_ROUTE,
    
            __RTNLGRP_MAX
    };
    
    
    int
    main()
    {
      printf("RTNLGRP_NEIGH = %d\n", RTNLGRP_NEIGH);
    }
    

    which is hopefully much less confusing.

    The #defines mixed into the enum definition have no effect to the enum definition. It doesn't matter where the #defines are located. They could (and probably should) have been placed before or after the definition.

    /* RTnetlink multicast groups */
    enum rtnetlink_groups {
            RTNLGRP_NONE,
            RTNLGRP_LINK,
            RTNLGRP_NOTIFY,
            RTNLGRP_NEIGH,
            RTNLGRP_TC,
            RTNLGRP_IPV4_IFADDR,
            /* ... */
            RTNLGRP_PHONET_ROUTE,
            __RTNLGRP_MAX
    };
    
    #define RTNLGRP_NONE            RTNLGRP_NONE
    #define RTNLGRP_LINK            RTNLGRP_LINK
    #define RTNLGRP_NOTIFY          RTNLGRP_NOTIFY
    #define RTNLGRP_NEIGH           RTNLGRP_NEIGH
    #define RTNLGRP_TC              RTNLGRP_TC
    #define RTNLGRP_IPV4_IFADDR     RTNLGRP_IPV4_IFADDR
    #define RTNLGRP_PHONET_IFADDR   RTNLGRP_PHONET_IFADDR
    /* ... */
    #define RTNLGRP_PHONET_ROUTE    RTNLGRP_PHONET_ROUTE
    #define RTNLGRP_MAX     (__RTNLGRP_MAX - 1)
    

    The reason they wrote this weired code is probably that they wanted to refactor old code using

    #define RTNLGRP_NONE          0
    #define RTNLGRP_LINK          1
    #define RTNLGRP_NOTIFY        2
    #define RTNLGRP_NEIGH         3
    #define RTNLGRP_TC            4
    #define RTNLGRP_IPV4_IFADDR   5
    /* ... */
    

    to use an enum instead. But because existing code might rely on the fact that the identifiers are macros (such as testing #ifdef RTNLGRP_NEIGH) they wanted to provide macros with the same value. Note that this approach is flawed, however, because the preprocessor won't know the value of the constant so you cannot do things like #if RTNLGRP_NEIGH >= 3 which you could, had RTNLGRP_NEIGH been #defined to 3 literally. So, in essence, their approach combines the disadvantages of using macros (name-space pollution) with those of using enums (not available at pre-processing time).

    A maybe more useful pattern I have seen before is to #define the constants to actual integers.

    enum rtnetlink_groups {
            RTNLGRP_NONE
    #define RTNLGRP_NONE            0
            = RTNLGRP_NONE,
            RTNLGRP_LINK
    #define RTNLGRP_LINK            1
            = RTNLGRP_LINK,
            RTNLGRP_NOTIFY
    #define RTNLGRP_NOTIFY          2
            = RTNLGRP_NOTIFY,
            RTNLGRP_NEIGH
    #define RTNLGRP_NEIGH           3
            = RTNLGRP_NEIGH,
            RTNLGRP_TC
    #define RTNLGRP_TC              4
            = RTNLGRP_TC,
            RTNLGRP_IPV4_IFADDR
    #define RTNLGRP_IPV4_IFADDR     5
            = RTNLGRP_IPV4_IFADDR,
            /* ... */
    };
    

    which will be pre-processed to the following.

    enum rtnetlink_groups {
            RTNLGRP_NONE
    
            = 0,
            RTNLGRP_LINK
    
            = 1,
            RTNLGRP_NOTIFY
    
            = 2,
            RTNLGRP_NEIGH
    
            = 3,
            RTNLGRP_TC
    
            = 4,
            RTNLGRP_IPV4_IFADDR
    
            = 5,
    
    };
    

    Note that here, it is critical that the #defines are mixed into the enum definition, otherwise we'd get invalid code such as 3 = 3, instead of the desired RTNLGRP_NEIGH = 3.

    Oh, and please don't use __RTNLGRP_MAX as an identifier. Names containing two adjacent underscores or beginning with an underscore followed by an upper-case letter are reserved by the C standard. Using them in your own code leads to undefined behavior.

    0 讨论(0)
  • 2021-01-21 04:06

    The value of RTNLGRP_NEIGH will be 3 (it is the fourth enumeration constant: RTNLGRP_NONE has the value 0, RTNLGRP_LINK has the value 1, and RTNLGRP_NOTIFY has the value 2).

    The #define stuff is somewhat weird — it is the sort of thing that's apt to make people want to stop you using the C pre-processor.

    The idea is that it gives you a macro for RTNLGRP_NEIGH that can be tested, but the expansion of the macro is the enumeration constant (spelled the same). There isn't an infinite loop in the expansions because once a macro has been expanded, it is not expanded again while the replacement text is being rescanned.

    So, the upshot is that you can write:

    #ifdef RTNLGRP_NEIGH
       …code using RTNLGRP_NEIGH…
    #endif
    
    0 讨论(0)
提交回复
热议问题