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
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 #define
d 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 #include
d 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 #define
s mixed into the enum
definition have no effect to the enum
definition. It doesn't matter where the #define
s 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 #define
d to 3
literally. So, in essence, their approach combines the disadvantages of using macros (name-space pollution) with those of using enum
s (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 #define
s 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.
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