​ 条件编译(也称为预处理)是一种在编译阶段控制代码是否包含进最终编译单元的技术。这通常通过使用预处理器指令(如#if, #ifdef, #ifndef, #else, #elif, 和 #endif)来完成。而条件编译通常是和宏联系在一起,因此说宏带有不用来回切换,宏替换发生在编译的预处理阶段,省区函数切换的时间花销,没有实参形参计算等优点。

​ 因此本次主要记录条件编译,并同时记录宏定义所需要注意的部分细节。

第一种形式

#ifdef MACRO
	some statements
#endif

如果定义了宏 MACRO,则编译some statements,否则不编译。


例如下面这个简单函数:

#include <stdio.h>
int main(int argc, char **argv)
{
    printf("条件编译的用法演示\n");
#ifdef HEL
    printf("Hello world\n");
#endif
    return 0;
}

当正常编译时,没有执行宏定义的选项,会出现下面的情况:

rice@rice:/mnt/f/$ gcc open.c -o open
rice@rice:/mnt/f/$ ./open
条件编译的用法演示

当编译时添加-HEL选项,提前增加DEL的宏定义,满足#ifdef的条件,结果如下:

rice@rice:/mnt/f/$ gcc open.c -DHEL -o open
rice@rice:/mnt/f/$ ./open
条件编译的用法演示
Hello world

第二种形式

#ifnodef MACRO
	some statements
#endif

​ 如果没定义宏 MACRO,则编译some statements,否则不编译。与第一种形式正好相反,最常用于头文件中,使得头文件的内容不会被重复包含。


#ifndef GET_TIME_H
#define GET_TIME_H

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "font.h" //字体函数

void get_Time(int *lcd_mp);

#endif

​ 在我的get_Time.h头文件中,使用了#ifndef, #define, 和 #endif这三个预处理器指令来确保头文件的内容只被包含一次,即使在多个源文件中多次包含了此头文件解释如下:

  1. #ifndef GET_TIME_H:检查GET_TIME_H这个宏是否已经被定义。如果GET_TIME_H没有被定义,那么编译器会处理它和与之对应的#endif之间的代码,即引用了包含的头文件。

  2. #define GET_TIME_H:这是一个宏定义指令,它定义了一个名为GET_TIME_H的宏,但不为其分配任何值(这样的宏通常被称为“标记”或“标记宏”)。GET_TIME_H被定义成了空的,这个指令的目的是防止在头文件被再次包含时再次包含头文件的内容。

  3. #endif:与#ifndef对应关系,标志着#ifndef条件编译块的结束。

    另外,作为一个宏,目前接触到了几种常见写法,不外乎几个小细节:字母全部大写,符号改成"_",且首尾增加 "_",例如上述头文件条件编译宏还可以写为:_GET_TIME_H_

第三种形式

#if expression
	some statements
#endif

​ 如果expression为真(非零),则编译some statements,否则不编译。如果expression是一个宏,则必须是一个整数或一个整形表达式。

下面是Linux源码部分内容

# if defined __USE_MISC || defined __USE_XOPEN
#  define S_ISVTX	__S_ISVTX
# endif

在Linux源码可经常看到条件编译的内容,在之后调试BUG,函数封装的头文件中也会经常用到,需要熟练掌握。

宏的应用

​ 宏分为带参数和不带参数的宏。不带参数的宏非常简单,是在使用的时候进行简单的文本替换,不再赘述。需要特别掌握的是带参数的宏。以下利用一个例子,来总结带参数宏使用的几个要点:

#define MUL1(a, b) a *b + a *b           
#define MUL2(a, b) (a) * (b) + (a) * (b) // 每个参数增加括号
#define MUL3(a, b)                   \
    ({                               \
        int aa = a;                  \
        int bb = b;                  \
        ((aa) * (bb) + (aa) * (bb)); \
    })

使用时同样是文本替换,却能实现不一样的结果,例如上述宏定义,引用时,代码及得到的结果如下:

int main(int argc, char **argv)
{
    int a=1;
    int b=2;
    int c1 = 0, c2 = 0, c3 = 0;
    c1 = MUL1(a+a, b+b);
    c2 = MUL2(a+a, b+b);
    c3 = MUL3(a+a, b+b);
    printf("c1=%d c2=%d c3=%d", c1, c2, c3);
    return 0;
}

得到的结果:c1=10 c2=16 c3=16

由于宏定义只进行文本替换,很容易看出三者的区别,预处理后,文本替换如下:

    c1 = MUL1(a, b)=1+1*2+2+1+1*2+2;
    c2 = MUL2(a, b)=(1+1)*(2+2)+(1+1)*(2+2);
    c3 = MUL3(a, b)=(1+1)*(2+2)+(1+1)*(2+2); 

再举个例子,代码及得到的结果如下:

int main(int argc, char **argv)
{
    int a = 1;
    int b = 2;
    int a1 = 1;
    int b1 = 2;
    int a2 = 1;
    int b2 = 2;
    int c1 = 0, c2 = 0, c3 = 0;
    c1 = MUL1(a, b++);
    c2 = MUL2(a1, b1++);
    c3 = MUL3(a2, b2++);
    printf("c1=%d c2=%d c3=%d", c1, c2, c3);
    return 0;
}

得到的结果:c1=5 c2=5 c3=4

由于宏定义只进行文本替换,预处理后,文本替换如下:

    c1 = MUL1(a, b)=a*b++ +a*b++=1*2+1*3=5
    c2 = MUL2(a1, b1)=(a1)*(b1++)+(a1)*(b1++)=1*2+1*3=5
    c3 = MUL3(a2, b2)=   
        ({                               \
        int aa = a2;                  \
        int ba = b2++;                  \
        ((a1) * (b1) + (a1) * (b1)); \
    })				=1*2+1*2=4	

因此,对于宏定义可得到如下小结

1.对于带参数的宏定义,切记要对于每个元素增加括号,以免运算优先级影响结果。

2.对于在宏定义中会多次出现的参数,应避免。若实在避免不了,可另外定义{(...)}复合语句,利用其他参数代替,且避免使用前缀或后缀运算符(例如“++”,“--”)等。