grape
全部视频:https://segmentfault.com/a/11...
原视频地址:http://replay.xesv5.com/ll/24...
引入
我们知道宏定义的优点有方便程序的修改,提高程序运行效率等等。并且在我们日常的代码学习中,我们会碰到过很多很多的宏定义。针对这些宏定义,我们通常都是秉承着“宏即是替换”的“法则”来进行分析。然而,对于一些简单的宏定义来说,我们直接进行替换即可完美的解决问题,但是针对于一些复杂的宏定义来说,我们会发现,替换也是有些门道的。那么,我们今天就来探索一下宏定义的神奇吧。
宏的基础知识
一、宏替换基础知识:
#define 宏名 字符串
#define 宏名(形参列表) 字符串
允许宏带有参数,在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数
二、C宏展开的几个注意事项:
- 每次宏展开的结果会被重复扫描,知道没有任何可展开的宏为止。
- 每展开一个宏,都会记住这次展开,在这个宏展开的结果及其后续展开中,不再对相同的宏做展开。
-
带参数的宏,先对参数做展开,除非定义体中包含#或##
a. '#'表示将后续标识转化为字符串。 b. '##'标识将两个标识连接成一个标识符。 c. 注意参数展开的结果中即使有逗号,也不要视为参数的分隔符。
- 如果宏定义中带有参数,而代码中出现同样标识时没有参数,不视为宏。
示例
-
首先我们看一个最简单的替换:
#include
#define foo(bar) bar int main() { printf("%s\n",foo("grape")); return 0; } 结果相信大家一眼就可以看出来,是的输出“grape”,如图所示:
-
对应于注意事项中的的一项,展开所有的宏,我们来看这样一个代码:
#include
#define foo(bar) bar1 #define bar1 "hello" int main() { printf("%s\n",foo("grape")); return 0; } 结果是什么呢?
好的,结果和大家想的一样,就是hello,如图所示: -
继续,对于第二个注意事项,首先我们分析一下这个事项是为什么。相信大家都知道递归,倘若一个递归没有结束条件会怎么样,结果肯定是无限的执行下去,如果,我们的宏定义也会出现这个情况,那。。。读者自行脑补吧。基于这个场景我们来看看这第二条规则,我们看一下这种情况,当然为了简单,这段代码是不可执行的:
#define foo foo bar
我们来看这个foo的定义,如果我们不知道这项规则,这段代码被我们来解析,按照替换来讲,我们是不是会认为是"... bar bar foo ..."这样子?然而真实的情况是这样子的:
foo //|->foo bar //| |~ |->bar bar foo //| |-> foo bar bar foo (至此展开完毕)
所以,同一个宏定义是不可循环展开的。
-
对于#和##的注意,在我们的日常代码学习中,我们很少遇见#和##,所以相信大家对此都十分陌生,现在让我们来看看它究竟有什么作用。见代码:
#include
#define f(a,b) a##b #define g(a) #a #define h(a) g(a) int main() { printf("%s\n",h(f(1,2))); //result1 printf("%s\n",g(f(1,2))); //result2 return 0; } 大家可以先看一下代码,考虑一下result1和result2会输出什么?
结果如图所示:然后我们可以想一下,如果没有#和##会输出?
#include
#define f(a,b) b #define g(a) a #define h(a) g(a) int main() { printf("%d\n",f(1,2)); printf("%d\n",h(f(1,2))); printf("%d\n",g(f(1,2))); return 0; } 结果如图所示:
对比两者我们会发现#和##的作用。即带参数的宏执行时,我们通常先对参数的宏进行展开,但是,在参数的宏中拥有#或者##的时候,会最后才进行展开。
-
第四点注意事项,就会很容易理解,举个例子,声明一个有入参的函数,如果你只去调用函数名会出现什么问题?当然,还有另外一种情况,例如:
#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size, static const uint32_t bin_data_size[] = { ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y) }; #define ZEND_MM_BINS_INFO(_, x, y) \ _( 0, 8, 512, 1, x, y) \ _( 1, 16, 256, 1, x, y) \ _( 2, 24, 170, 1, x, y) \ _( 3, 32, 128, 1, x, y) \ _( 4, 40, 102, 1, x, y) \ _( 5, 48, 85, 1, x, y) \ _( 6, 56, 73, 1, x, y) \ _( 7, 64, 64, 1, x, y) \ _( 8, 80, 51, 1, x, y) \ _( 9, 96, 42, 1, x, y) \ _(10, 112, 36, 1, x, y) \ _(11, 128, 32, 1, x, y) \ _(12, 160, 25, 1, x, y) \ _(13, 192, 21, 1, x, y) \ _(14, 224, 18, 1, x, y) \ _(15, 256, 16, 1, x, y) \ _(16, 320, 64, 5, x, y) \ _(17, 384, 32, 3, x, y) \ _(18, 448, 9, 1, x, y) \ _(19, 512, 8, 1, x, y) \ _(20, 640, 32, 5, x, y) \ _(21, 768, 16, 3, x, y) \ _(22, 896, 9, 2, x, y) \ _(23, 1024, 8, 2, x, y) \ _(24, 1280, 16, 5, x, y) \ _(25, 1536, 8, 3, x, y) \ _(26, 1792, 16, 7, x, y) \ _(27, 2048, 8, 4, x, y) \ _(28, 2560, 8, 5, x, y) \ _(29, 3072, 4, 3, x, y)
我们在第一次看到_BIN_DATA_SIZE只认为是一个形量传入到函数中,没有做宏替换,在_替换之后会被扫描到重新做替换。具体的解析见【PHP源码学习】2019-03-11 PHP内存管理3笔记。
结尾
在我们的工作或者学习中,会出现很多复杂的宏替换,只要我们认定“宏即是替换”以及记住以上注意事项,那么一切复杂宏替换都是纸老虎。