问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言预处理知识详解:从宏定义到条件编译

创作时间:
作者:
@小白创作中心

C语言预处理知识详解:从宏定义到条件编译

引用
CSDN
1.
https://m.blog.csdn.net/2301_77954967/article/details/137122271

C语言预处理是C语言编译过程中的一个重要环节,它主要负责处理源代码中的预处理指令,如宏定义、条件编译等。预处理指令可以提高代码的可读性和可维护性,同时也可以实现代码的复用和优化。本文将详细介绍C语言预处理的主要内容,包括预定义符号、宏定义、#和##运算符以及条件编译等。

1. 预定义符号

在C语言中内置了一些预定义符号,可以直接使用,这些符号实在预处理期间处理的,并且这些符号都是C语言ANSIC里收集的。但是随着编译器版本的升级,有些C语言内置预定义可能会被忽视掉。

2. define

2.1 define 定义常量

基本语法:

#define name stuff

例如:

#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
#define DEBUG PRINT printf("file:%s\tline:%d\tdate:%s\ttime:%s\n",__FILE__,-__LINE__,__DATE__,__TIME__)

思考:需要在定义后加上“;”吗?

例如:

#define MAX 1000
#define MAX 1000;

建议是不加上,可能会导致原来语句顺序发生错乱。

2.2 define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。

申明方式:

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意1:parament-list 的左括号必须紧贴在name的右边,一旦有空格在中间,就会让宏判定 ( parament-list ) 属于后面的 stuff

注意2:宏一旦运行后会直接替换 parament-list 的内容到stuff里, stuff 及 parament-list 里的计算符的优先级就可能会导致计算顺序发生变化,所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

例如,现在有这样一个宏:

#define double(x) x*x

没有注意用括号区分优先级的话,当 x 为 x+1 时,

这个宏的运算的顺序就是 x+1*x+1,即 x + x +1,最终导致结果发生较大偏差

但如果我们定义宏为:

#define double(x) (x)*(x)

就不会在导致这种情况

注意3:当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。例如:++ , – 等符号

以下举例:

#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main()
{
    int a = 3;
    int b = 5;
    int m = MAX(a++, b++);
    //相当于替换成
    //int m = MAX(a++,b++) ((a++)>(b++)?(a++):(b++))
    //a++和b++各会执行两次
    printf("m = %d\n", m);//6
    printf("a = %d\n", a);//4
    printf("b = %d\n", b);//7
    return 0;
}

宏替换规则总结

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

宏和函数的对比

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者那我们平时的一个习惯是:

把宏名全部大写

函数名不要全部大写

其中内置宏 offsetof 比较特殊,它是红,但是全部小写,它的作用是计算结构体成员相对结构体起始位置的偏移量

#undef

用来移除一个宏定义

例如下图

原来没有移除 MAX 的时候,是可以打印出来的,但是一移除后就显示未定义标识符。

3. #和##

3.1

#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。

#运算符所执行的操作可以理解为”字符串化“。

例如:当我们有一个变量 int a = 10;的时候,我们想打印出:the value of ais 10

就可以写下面这个宏

#define PRINT(n) printf("the value of "#n " is %d", n);

当我们把a替换到宏的体内时,就出现了#a,而#a就是转换为"a”,时一个字符串代码就会被预处理为:

printf("the value of ""a" " is %d", a);

结果就是

the value of a is 10

3.2

##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称为记号粘合

这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

这里我们想想,写一个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。

比如,我们相要写下面这两个函数,但是要不断地写他们的类型名,这里我们可以利用宏

int int_max(int x, int y)
{
    return x > y ? x : y;
}
float float_max(float x, float y)
{
    return x > y ? x:y;
}

我们相要写上面这两个函数,但是要不断地写他们的类型名,这样太繁琐了,这里我们可以利用宏

//宏定义
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}

其中##的作用是链接type和后面的_max,否则,当type被输入后,type_max 就会被认为时一个,使得函数名一直是 type_max

定义好宏后,我们放入类型名进去,就会产生新的函数名,然后可以进行使用

GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{
    //调⽤函数
    int m = int_max(2, 3);
    printf("%d\n", m);
    float fm = float_max(3.5f, 4.5f);
    printf("%f\n", fm);
    return 0;
}

最终结果是

3
4.500000

4. 条件编译(开关)

当我们在调试时需要写一些语句辅助,但是正式发布时可能是不带的,我们一般在正式使用时会注释掉,但这仅适用于小型代码,一旦代码量达到一个恐怖速度,就可能会有缺漏,并造成巨大的工作量,所以就用到了部分条件编译,用作开关

例如

#include <stdio.h>
#define __debug__
int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
#ifdef __debug__
        printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__debug__
    }
    return 0;
}

这里的代码,中间那条输出就是调试时观察用的,我们在开头定义宏,一旦不需要用掉,只需要把宏去了,就不会运行这行代码,这种方法在代码很长的时候很有用,用做开关。

除了这个 #ifdef 还有很多别的

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
    
2.多个分⽀的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif

C语言预处理是C语言编译过程中的一个重要环节,它主要负责处理源代码中的预处理指令,如宏定义、条件编译等。预处理指令可以提高代码的可读性和可维护性,同时也可以实现代码的复用和优化。通过本文的介绍,相信读者已经掌握了C语言预处理的主要内容和应用场景,可以更好地运用这些知识来编写高质量的C语言代码。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号