1

I have inherited some C code, and I am a bit rusty on macros. We use libdnet to create packets. I have code that does this:

ip_pack_hdr(
    /* hdr = */  &(pkt->ip),
    /* tos = */  0,                 // Fixed
    /* len = */  ICMP4_ECHO_PKT_LEN_NO_ETH + data_len, // Fixed
    /* id = */   0,                 // Dynamic (self)
    /* off = */  IP_DF,             // Fixed
    /* ttl = */  0,                 // Dynamic (caller)
    /* p = */    IP_PROTO_ICMP,     // Fixed
    /* src = */  src.addr_ip,       // Fixed
    /* dst = */  dst.addr_ip        // Fixed
);

and the definition of ip_pack_hdr in libdnet is

#define ip_pack_hdr(hdr, tos, len, id, off, ttl, p, src, dst) do {  \
    struct ip_hdr *ip_pack_p = (struct ip_hdr *)(hdr);      \
    ip_pack_p->ip_v = 4; ip_pack_p->ip_hl = 5;          \
    ip_pack_p->ip_tos = tos; ip_pack_p->ip_len = htons(len);    \
    ip_pack_p->ip_id = htons(id); ip_pack_p->ip_off = htons(off);   \
    ip_pack_p->ip_ttl = ttl; ip_pack_p->ip_p = p;           \
    ip_pack_p->ip_src = src; ip_pack_p->ip_dst = dst;       \
} while (0)

I am trying to understand what exactly happens when the macro is called. I understand from this SO question why there is a do-while(0) loop, but what I don't understand is does this macro modify my data in place? It is supposed to act like a function but where does the final value of ip_pack_p get stored?

Mark
  • 2,058
  • 2
  • 35
  • 64
  • 2
    "Macros" in C are a simple string substitution (including substitution of arguments) done by the C pre-processor (on Linux, that would be`cpp foo.c`) before compilation is done. Run just the C pre-processor on your source to see exactly how the macro is expanded. It doesn't exactly act like a function. – lurker Jun 22 '17 at 16:19
  • 4
    Given the above, macros are not "called". – Eugene Sh. Jun 22 '17 at 16:20
  • @EugeneSh.: When are macros ever "called"? – too honest for this site Jun 22 '17 at 19:43
  • That macro is calling for trouble and the cast already is. Don't get too fancy with macros! – too honest for this site Jun 22 '17 at 19:43
  • @Olaf "Given the sky is blue, it is not red", doesn't imply it is red ever. – Eugene Sh. Jun 22 '17 at 19:47
  • @EugeneSh.: If your are sure it never is read, why not be clear and say "it is never red"? I was hoping you know an application where a macro is called. From my knowledge a macro **never** is "called", so I wondered why you left a backdoor open in your statement. – too honest for this site Jun 22 '17 at 19:52
  • @Olaf Have you ever said something like "I will not eat this carrot, I am not a rabbit" or "I am not going to jump down there, I don't have wings" or similar? Even knowing the antecedent can be never false and logically redundant? – Eugene Sh. Jun 22 '17 at 20:07
  • @EugeneSh.: We are not a food or pet Q&A site. For a programming problem if something is **always** wrong, one could and should be clear about it. "It is not a calll _here_ leave unnecessary uncertainty. Simply: A macro is **never** called is clear and leaves no room for speculation.- Especially for a beginner who apparently has missconceptions about macros already. (oh, and why should I ever have said that? How do you know I'm not a rabbit with wings? Mabe my guinea pig avatar is just to confuse you - not that guinea pigs don't like carrots, too:-) – too honest for this site Jun 22 '17 at 20:50

3 Answers3

6

There is no such thing as "calling" a macro, because macros are expanded at compile time, while calling, if any, happens at run time. Preprocessor, the first state of C compiler, expands your macro for use by the translating stage of C compiler.

When prprocessor expands your macro, it becomes this:

struct ip_hdr *ip_pack_p = (struct ip_hdr *)(&(pkt->ip));
ip_pack_p->ip_v = 4;
ip_pack_p->ip_hl = 5;
ip_pack_p->ip_tos = 0;
ip_pack_p->ip_len = htons(ICMP4_ECHO_PKT_LEN_NO_ETH + data_len);
ip_pack_p->ip_id = htons(0);
ip_pack_p->ip_off = htons(IP_DF);
ip_pack_p->ip_ttl = 0;
ip_pack_p->ip_p = IP_PROTO_ICMP;
ip_pack_p->ip_src = src.addr_ip;
ip_pack_p->ip_dst = dst.addr_ip;

The entire block of code is wrapped in do/while(0) (why?). Macro arguments that you provided to ip_pack_hdr are copied verbatim into places indicated by corresponding macro parameters.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thanks a lot, that really helps me understand this. – Mark Jun 22 '17 at 16:29
  • Does the macro expansion include the `/* hdr = */`? Perhaps not. – chux - Reinstate Monica Jun 22 '17 at 17:49
  • @chux That is an excellent question - I'm nearly certain that preprocessor strips all comments, including the ones inside macro expansions. I'd have to give it a try to see if it is happening or not. – Sergey Kalinichenko Jun 22 '17 at 18:47
  • 1
    Hmm, I think C strips comments and replaces them with whitespace. I tried `#define OC /* #define CC */` and _that_ does not work to define `CC`. So macros are a text substitution _after_ converting C style comments into spaces. §5.1.1.2 3-4 – chux - Reinstate Monica Jun 22 '17 at 18:50
1

The code gets substituted before compilation. So it's as if you write the code directly where you use the macro. A macro is not like a function, and in that sense you can't "call" a macro.

The do { } while (0) is just a simple a way to enclose the block of code, note that this will never loop, it's just executed once. It also creates a scope, and allows a ; to be placed after the macro, so it looks like a function call, but it's not.

Also, the code is expanded in a single line, which makes debugging very difficult. You can see the result of preprocessing by calling the compiler with the appropriate flag, inspect the generated file or code and you will understand it better.

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
1

Calling a macro in C, what is the result?

A macro is text substitution. The result depends on how it if formed.

Note: macro text substitution occurs after comments are changed into white-spaces.

Consider int putc(int c, FILE *stream);. An implementation may make putc() a true function or implement as a macro. In the latter case, the result in an int because the macro was design that way.

Consider the following. The macro SEMI does not do much as it is not trying to emulate a function and does not form a returnable result

#define SEMI(a) ;
int main() {
  SEMI(nothing)
}

OP's macro ip_pack_hdr() simple substitutes

ip_pack_hdr(
/* hdr = */  &(pkt->ip),
/* tos = */  0,                 // Fixed
/* len = */  ICMP4_ECHO_PKT_LEN_NO_ETH + data_len, // Fixed ...
....

becomes one long line

do { struct ip_hdr *ip_pack_p = (struct ip_hdr *)( &(pkt->ip)); ip_pack_p->ip_v = 4; ip_pack_p->ip_hl = 5; ip_pack_p->ip_tos = 0; ip_pack_p->ip_len = htons( ICMP4_ECHO_PKT_LEN_NO_ETH + data_len); ...
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256