2

In many system header files, we can see

/* Standard well-defined IP protocols.  */
enum
  {
    IPPROTO_IP = 0,    /* Dummy protocol for TCP.  */
#define IPPROTO_IP     IPPROTO_IP
    IPPROTO_ICMP = 1,  /* Internet Control Message Protocol.  */
#define IPPROTO_ICMP   IPPROTO_ICMP
...
}
enum EPOLL_EVENTS
  {
    EPOLLIN = 0x001,
#define EPOLLIN EPOLLIN
    EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
...
}

What's the purpose of the #define?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
mackong
  • 23
  • 5

2 Answers2

4

Your code seems to be taken from Linux's uapi/linux/in.h header file. Those defines were introduced in this commit in Linux v3.12-rc1. As the commit message explains:

The kernel promises not to break the UAPI ABI so I don't
see why we can't just have the two userspace headers
coordinate?

If you include the kernel headers first you get those,
and if you include the glibc headers first you get those,
and the following patch arranges a coordination and
synchronization between the two.

Here's also the sibling commit in glibc for reference.

So these were added to "sync" with the userspace C library (glibc) and avoid conflicts. However, this now raises the question: why did glibc have those defines to begin with? Well, just to make use of the #ifdef directive in several parts of the library. For example, you can see in glibc's sysdeps/posix/getaddrinfo.c:

static const struct gaih_typeproto gaih_inet_typeproto[] =
{
  { 0, 0, 0, false, "" },
  { SOCK_STREAM, IPPROTO_TCP, 0, true, "tcp" },
  { SOCK_DGRAM, IPPROTO_UDP, 0, true, "udp" },
#if defined SOCK_DCCP && defined IPPROTO_DCCP
  { SOCK_DCCP, IPPROTO_DCCP, 0, false, "dccp" },
#endif
#ifdef IPPROTO_UDPLITE
  { SOCK_DGRAM, IPPROTO_UDPLITE, 0, false, "udplite" },
#endif
#ifdef IPPROTO_SCTP
  { SOCK_STREAM, IPPROTO_SCTP, 0, false, "sctp" },
  { SOCK_SEQPACKET, IPPROTO_SCTP, 0, false, "sctp" },
#endif
  { SOCK_RAW, 0, GAI_PROTO_PROTOANY|GAI_PROTO_NOSERVICE, true, "raw" },
  { 0, 0, 0, false, "" }
};

Since those were also exported, when including netinet/in.h users could rely on those definitions in a similar way in their userspace programs as well.

So, to summarize: since users could either include glibc's netinet/in.h or Linux's linux/in.h, those files were synchronized, with Linux adapting to glibc, and adding the same defines provided by the glibc header.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • 2
    @Wolf it was easy enough to notice that those were straight out of the linux kernel `include/uapi` headers, which is what I would call "system headers". – Marco Bonelli Feb 25 '21 at 10:26
  • Only after your comment, I had the idea to google for the identifiers, getting lots of results:) – Wolf Feb 25 '21 at 10:38
  • @Wolf I used elixir.bootlin.com just FYI, it's a pretty useful tool ([example](https://elixir.bootlin.com/linux/v3.12-rc1/C/ident/IPPROTO_IP)). – Marco Bonelli Feb 25 '21 at 10:40
0

By simply including such a header, there will be no effect to the constants being generated. This is why this looks so strange to most programmers who are familiar with header definitions.

System headers

#define and enum, both lead to the same kind of compile-time constants. I think developers want to ensure that any redefines that might be scattered in a (maybe older) code base would pop up as a compiler warning. A possible scenario could be a system header supports more definitions of the same kind after an upgrade for which depending code already has divergent definitions.

Another point would be to make the existence of constants available to the pre-processor by #if or #ifdef making compile-time branching possible.

Linux kernel

Some system headers have to be able to also work under complicated conditions without breaking code, for instance if wide-spread libraries exist that interfere with the core system, this is what Marco Bonelli has shown.

Normal headers

Doing things like this in "normal" headers (those limited to and under control of just one project) would only lead to confusion.

You normally decide to using #define or enum style for compile-time constants. If you change decision once, do the change to all constants of a given domain at the same time.

A strange way to get benefit from having both here could be, to let the enum syntax increase a counter automatically. This is done by omitting explicit values for the enumerators:

enum {
    IPPROTO_IP,
#define IPPROTO_IP     IPPROTO_IP
    IPPROTO_ICMP,
#define IPPROTO_ICMP   IPPROTO_ICMP
// ...
};

This would lead to IPPROTO_IP being 0 and IPPROTO_ICMP being 1.

Wolf
  • 9,679
  • 7
  • 62
  • 108