神奇的宏替换

  在C语言中可以使用#define来指定一个标识符(宏名)代表一个字符串。宏定义有两种方式,一种有参数宏定义,一种无参数宏定义。

1
2
3
4
//无参数宏定义
#define 宏名 字符串
//有参数宏定义
#define 宏名(参数) 字符串

  关于宏的几点说明:
1、宏名一般大写;
2、宏是预编译处理不做语法检查和计算,仅替换;
3、宏定义末尾不加分号;
4、宏使用#define定义,使用#undef取消定义;
5、宏定义可以嵌套,不会递归替换;
6、字符串""中不包含宏,宏定义不分配内存,变量定义分配内存;
7、宏名和参数间的括号不能有空格;
8、#用于在宏定义的参数两端加上""##用于拼接宏;
9、每次宏展开的结果会被重复扫描,直到没有任何可以展开的宏为止;
10、每展开一个宏,都会记住这次展开,在这个宏展开的结果和后续展开中不会重复展开相同的宏;
11、带参数的宏,先对参数展开,除非宏定义体中有###
  针对8,9条给出一个说明例子:demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
#define foo foo bar
#define bar bar bar foo
int main()
{
foo;
}
/*
先展开foo => foo bar,因为foo已经展开过了,之后的foo不再展开
此时展开bar => foo bar bar foo

通过 gcc -E demo.c 得到如下结果:
int main()
{
foo bar bar foo;
}
说明结论正确。
*/

  学习了宏定义以后我们来看一下PHP源码中的宏替换,PHP源码中有一个内存分配的宏替换,这个宏的位置在/usr/src/php-7.1.6/Zend/zend_alloc.h文件的106行:

1
2
3
4
5
6
7
8
9
10
11
# define ZEND_ALLOCATOR(size) \
ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_START, size, y) \
((size <= ZEND_MM_MAX_LARGE_SIZE) ? _emalloc_large(size) : _emalloc_huge(size)) \
ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_END, size, y)

# define _emalloc(size) \
(__builtin_constant_p(size) ? \
ZEND_ALLOCATOR(size) \
: \
_emalloc(size) \
)

  我们可以看到里面有四个宏定义,其中ZEND_MM_BINS_INFOZEND_MM_MAX_LARGE_SIZE这两个宏在/usr/src/php-7.1.6/Zend/zend_alloc_sizes.h中,详情如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifndef ZEND_ALLOC_SIZES_H
#define ZEND_ALLOC_SIZES_H

#define ZEND_MM_CHUNK_SIZE (2 * 1024 * 1024) /* 2 MB */
#define ZEND_MM_PAGE_SIZE (4 * 1024) /* 4 KB */
#define ZEND_MM_PAGES (ZEND_MM_CHUNK_SIZE / ZEND_MM_PAGE_SIZE) /* 512 */
#define ZEND_MM_FIRST_PAGE (1)

#define ZEND_MM_MIN_SMALL_SIZE 8
#define ZEND_MM_MAX_SMALL_SIZE 3072
#define ZEND_MM_MAX_LARGE_SIZE (ZEND_MM_CHUNK_SIZE - (ZEND_MM_PAGE_SIZE * ZEND_MM_FIRST_PAGE))

/* num, size, count, pages */
#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)

#endif /* ZEND_ALLOC_SIZES_H */

  我们可以把这个宏从代码里面摘出来test_zend.c如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "zend_alloc_sizes.h"
#include<stdio.h>

# define _ZEND_BIN_ALLOCATOR_SELECTOR_START(_num, _size, _elements, _pages, size, y) \
((size <= _size) ? _emalloc_ ## _size() :
# define _ZEND_BIN_ALLOCATOR_SELECTOR_END(_num, _size, _elements, _pages, size, y) \
)

# define ZEND_ALLOCATOR(size) \
ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_START, size, y) \
((size <= ZEND_MM_MAX_LARGE_SIZE) ? _emalloc_large(size) : _emalloc_huge(size)) \
ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_END, size, y)

# define _emalloc(size) \
(ZEND_ALLOCATOR(size))

int main()
{
_emalloc(size);
}

  接下来我们来一步步展开这个宏:
1、展开 _emalloc(size)
  展开以后发现ZEND_ALLOCATOR(size)这个宏
2、展开 ZEND_ALLOCATOR(size)
  展开以后发现ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_START, size, y)这个宏
3、展开 ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_START, size, y)
  结合前面的ZEND_MM_BINS_INFO(_, x, y)得到如下结果:

1
2
3
4
5
6
_ZEND_BIN_ALLOCATOR_SELECTOR_START(0, 8, 512, 1, size, y) \
_ZEND_BIN_ALLOCATOR_SELECTOR_START(1, 16, 256, 1, size, y) \
.
.
.
_ZEND_BIN_ALLOCATOR_SELECTOR_START(29, 3072, 4, 3, size, y) \

4、展开 _ZEND_BIN_ALLOCATOR_SELECTOR_START(_num, _size, _elements, _pages, size, y)

1
2
3
((size <= 8) ? _emalloc_8() : ((size <= 16 ? _emalloc_16() :
.........................................................
((size <= 3072) ? _emalloc_3072() :

5、展开 ZEND_MM_MAX_LARGE_SIZE

1
((size <= ((2 * 1024 * 1024) - ((4 * 1024) * (1)))) ? lloc_large(size) : _emalloc_huge(size))

6、展开 ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_END, size, y)

1
2
3
4
5
6
_ZEND_BIN_ALLOCATOR_SELECTOR_END(0, 8, 512, 1, size, y) \
_ZEND_BIN_ALLOCATOR_SELECTOR_END(1, 16, 256, 1, size, y) \
.
.
.
_ZEND_BIN_ALLOCATOR_SELECTOR_END(29, 3072, 4, 3, size, y) \

7、展开 _ZEND_BIN_ALLOCATOR_SELECTOR_END(_num, _size, _elements, _pages, size, y)

1
)))))))))))))))))))))))))))))))

8、到此所有宏展开完毕,组合以后得到如下结果

1
2
3
(((size <= 8) ? _emalloc_8() : ((size <= 16 ? _emalloc_16() :
.........................................................
((size <= 3072) ? _emalloc_3072() : ((size <= ((2 * 1024 * 1024) - ((4 * 1024) * (1)))) ? lloc_large(size) : _emalloc_huge(size)) )))))))))))))))))))))))))))))))

9、执行 gcc -E test_zend.c 得到结果如下

1
2
3
4
int main()
{
(((size <= 8) ? _emalloc_8() : ((size <= 16) ? _emalloc_16() : ((size <= 24) ? _emalloc_24() : ((size <= 32) ? _emalloc_32() : ((size <= 40) ? _emalloc_40() : ((size <= 48) ? _emalloc_48() : ((size <= 56) ? _emalloc_56() : ((size <= 64) ? _emalloc_64() : ((size <= 80) ? _emalloc_80() : ((size <= 96) ? _emalloc_96() : ((size <= 112) ? _emalloc_112() : ((size <= 128) ? _emalloc_128() : ((size <= 160) ? _emalloc_160() : ((size <= 192) ? _emalloc_192() : ((size <= 224) ? _emalloc_224() : ((size <= 256) ? _emalloc_256() : ((size <= 320) ? _emalloc_320() : ((size <= 384) ? _emalloc_384() : ((size <= 448) ? _emalloc_448() : ((size <= 512) ? _emalloc_512() : ((size <= 640) ? _emalloc_640() : ((size <= 768) ? _emalloc_768() : ((size <= 896) ? _emalloc_896() : ((size <= 1024) ? _emalloc_1024() : ((size <= 1280) ? _emalloc_1280() : ((size <= 1536) ? _emalloc_1536() : ((size <= 1792) ? _emalloc_1792() : ((size <= 2048) ? _emalloc_2048() : ((size <= 2560) ? _emalloc_2560() : ((size <= 3072) ? _emalloc_3072() : ((size <= ((2 * 1024 * 1024) - ((4 * 1024) * (1)))) ? _emalloc_large(size) : _emalloc_huge(size)) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ));
}

  验证正确,这就是宏替换,看源码会有很多这样复杂的宏定义,只要认真仔细,都是可以轻松替换的。赶快去试试吧。