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)));
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,002评论 6 542
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,400评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,136评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,714评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,452评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,818评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,812评论 3 446
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,997评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,552评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,292评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,510评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,035评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,721评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,121评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,429评论 1 294
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,235评论 3 398
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,480评论 2 379

推荐阅读更多精彩内容