C语言陷阱之 #if 不存在的宏

发布时间 2024-01-12 18:03:02作者: 0xCAFEBABE

.

.

.

.

.

今天在使用 __BYTE_ORDER 宏判断字节序的时候,使用了如下的代码:

#include <stdio.h>
#include <stdint.h>

typedef struct relay_frame_st {
#if __BYTE_ORDER == __BIG_ENDIAN
    uint16_t version : 4;
    uint16_t seq : 12;
#elif __BYTE_ORDER == __LITTLE_ENDIAN
    uint16_t seq : 12;
    uint16_t version : 4;
#endif
    uint32_t a:1;
    uint32_t b:1;
    uint32_t c;
} __attribute__((packed)) Frame, *PFrame;

int main (int argc, char *argv[])
{
    printf("sizeof=%d\n", sizeof(Frame));
    Frame frame = {
        .seq = 0x123,
        .version = 0x4
    };
    #if __BYTE_ORDER == __BIG_ENDIAN
    printf("big end\n");
    #elif __BYTE_ORDER == __LITTLE_ENDIAN
    printf("little end\n");
    #else
    #error UNKNOWN BYTE ORDER
    #endif
    printf("frame=%X\n", *(uint16_t*)&frame);
    uint8_t *p = (uint8_t *) &frame;
    printf("%02X %02X\n", *p, *(p+1));
    return 0;
}

编译运行后结果是这样的:

$ gcc end.c && ./a.out 
sizeof=7
big end
frame=4123
23 41

 运行结果很奇怪,打印了 big end,但是 frame 的值却是按照小端的形式打印的:34 12。

在网上查资料,发现别人使用 __BYTE_ORDER 宏的时候包含了一个头文件,于是我也包含进去:

#include <endian.h>

 再编译运行一次:

$ gcc end.c && ./a.out 
sizeof=7
little end
frame=4123
23 41

这次正常了,看来是因为没有包含头文件导致宏未定义,所以进入了第一个条件分支。

但是恰恰问题就出现在这里:宏没有定义,为什么条件编译却进入了?非常反直觉,于是我加入了下面的代码进行测试:

#if aaaaa == bbbbb
printf("aaaaa\n");
#endif

编译运行,结果你猜怎么着?竟然真的进入条件编译了。

... 省略 ...
aaaaa
... 省略 ...

 

于是赶紧翻书查资料,发现在《C程序设计语言》找到这样一段话:

 

按照我的理解,当使用预处理指令  #if 判断某个宏的时候,如果宏的值没有定义,就会被替换为 0。于是赶紧做个实验:

#if bbbbb == 1
printf("bbbbb\n");
#elif hhhhhhhh == 0
printf("hhhhhhhh\n");
#endif

结果你猜怎么着?不存在的宏还真是和 0 相等。

... 省略 ...
hhhhhhhh
... 省略 ...

 

这样也就解释得通为什么当两个宏都不存在的时候,条件编译却被满足了。

因为两个宏都不存在,那么在预编译器看来条件就是 (0 == 0) 呀,结果当然是 1 啦。