C语言#define宏定义,你真的了解吗?

在C语言中,我们使用#define来定义宏。在C程序编译的预处理阶段,预处理器会把宏定义的符号替换成指定的文本。

不带参数的宏

关于宏最常见的就是用来定义数值常量的名称,即没有参数的宏定义,采用如下形式:

#define 宏名称 替换文本

例如:

#define ARRAY_SIZE  10
int data[ARRAY_SIZE];

当程序需要修改数组长度时,只需要修改宏定义即可,无需对程序中每一处用到数组长度的地方进行修改。

带参数的宏

你可以定义具有形参的宏,然后预处理器展开这类宏时,会将宏指定的实参替换文本中对应的形参。这有点像函数,故也叫做函数式宏定义、类函数宏,形式如下:

#define 宏名称([行参列表]) 替换文本
#define 宏名称([行参列表,]...) 替换文本

当宏被调用时,替换文本中的每个值都与形参列表相对应。另外C99标准允许定义有参略号的宏,省略号必须放在参数列表的后面,以表示可选参数。

当调用有可选参数的宏时,预处理器会将所有可选参数连同分隔它们的逗号打包在一起作为一个参数。在替换文本中,标识符 VA_ARGS 对应前面打包的可选参数。

//假设有个已经打开的日志文件,准备采用文件指针fp_log对其进行写入
#define printLog(...) fprintf(fp_log, __VA_ARGS__)
//使用printLog
printLog("%s: intVar=%d\r\n", __func__, intVar);

预处理器把最后一行的宏调用替换成下面一行代码:

fprintf(fp_log, "%s: intVar=%d\r\n", __func__, intVar);

带参宏的一些问题

#include <stdio.h>
#define SQUARE(x)   x * x
int main(int argc, char const *argv[])
{
    int a = 5;
    printf("%d\r\n", SQUARE(5));
    printf("%d\r\n", SQUARE(a+1));
    return 0;
}

​ 程序运行,第一条打印显而易见为25,第二条打印为多少呢?不是36而是11。我们通过"gcc -E a.c >a.txt",将预处理后的文件重定向到a.txt,来观察被替换的宏文本。

printf("%d\r\n", 5 * 5);
printf("%d\r\n", a+1 * a+1);

可见替换产生的表达式并没有按照预想的次序进行求值。可能大家会说,加上两个括号就解决了嘛:

#define SQUARE(x)  (x) * (x)

那么,为每个出现在替换文本中的参数加上括号就一定没问题了吗?看下面例子:

#include <stdio.h>
#define ADD(x)  (x) + (x)
int main(int argc, char const *argv[])
{
    int a = 5;
    printf("%d\r\n", 10 * ADD(5));
    return 0;
}

看似输出100,实际输出55。我们看下替换后的文本:

printf("%d\r\n", 10 * (5) + (5));

乘法运算在加法运算之前执行,所以结果为55。这个错误很容易修正:整个表达式加上括号:

#define ADD(x)  ((x) + (x))

所有对数值表达式进行求值的宏定义,为避免参数中操作符或邻近的操作符之间不可预料的互相作用,应对每个参数加括号,整个表达式也要加括号。

宏与函数

尽可能多的加括号就绝对不会有问题了吗?看下面例子:

#include <stdio.h>
#define MAX(a, b)   ((a) > (b) ? (a) : (b))
int main(int argc, char const *argv[])
{
    int a = 5;
    int b = 5;
    int m = MAX(++a, b);
    printf("%d\r\n", m);
    return 0;
}

m的值是多少?是不是相当于执行MAX(6, 5),然后输出6呢?答案是7,看下替换的文本:

int m = ((++a) > (b) ? (++a) : (b));

这里就是C语言中函数式宏定义的陷阱,传递参数++a会被展开到替换文本的每一个a处。所以,再多的括号也不可能确保万无一失。

那么,函数式宏定义较函数有什么优点吗?

  • 相较于函数的分配和释放栈、传参、传返回值等一系列操作,函数式宏定义的代码执行效率高。
  • 函数的参数必须声明为一种特定的类型,而宏是与类型无关的。以比较大小为例,如果我们需要比较整形、长整型、浮点型数值大小,由于C语言不支持函数重载,我们需要为每一种数据类型实现一个max()函数,显然使用宏定义要来的方便了。当然宏定义不检测参数类型也是把双刃剑,可能导致程序不安全,需要特别注意。
  • 还有一些函数无法实现的任务,比如说,如何将数据类型作为参数传递给函数?看宏定义怎么破:
#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
...
p = MALLOC(20, int);

替换后的文本:

p = ((int *)malloc((20) * sizeof(int)));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章