Closed xiaoliuzi closed 8 years ago
翻阅TCPL,发现如下代码,没有搞懂。
#define cat(x,y) x ## y
以上代码,宏调用,cat(var,123)将会生成var123. 但是,宏调用cat(cat(1,2),3)没有定义,##定义阻止了外层调用的参数扩展。 因此,它将生成下列记号串: cat(1,2)3 并且,)3不是一个合法的记号,它由第一个参数的最后一个记号与第二个参数的第一个记号连接而成。
问题:这里cat嵌套的宏函数的展开方式是从外到内还是从内到外呢?有什么规则吗? 已知:##在宏定义中,会在替换的过程中,把##以及##前后的空白符都将删除掉,以便将相邻记号连接起来形成一个新的记号。
而假如有如下代码,
#define xcat(x,y) cat(x,y)
我们就可以得到正确的结果。即:
xcat(xcat(1,2), 3)
将生成123。TCPL说的是:因为自身的扩展包不包含##运算符。
参考https://github.com/libuv/libuv/blob/v1.x/src/uv-common.c 中的宏定义调用 另外, UV_HANDLE_TYPE_MAP的参数不仅仅是XX,还可以是其他的宏定义。
http://c.biancheng.net/cpp/html/1487.html
预处理还需要运算符?有没有搞错?^_^, 没有搞错,预处理是有运算符,而且还不止一个:
## (双井号 )—— 连接运算符
#@ —— 字符化运算符。
接下来我们会分别说明一下他们的用法. 一、字符串化运算符 —— #
用于创建字符串,#运算符后面应该跟一个形参(中间可以有空格或Tab),例如:
puts(STR(Here is a Demo)); // 相当于puts("Here is a Demo");
常用实例,我们在调试代码的时候有时需要打印一些字符串的值,如下:
int main() { const char * pchName = "Gui xue"; Dump_Str(pchName); } 二、连接运算符 —— ##
用于将两个Token连接成一个Token。这里提到一个需要概念Token ,先说明一下什么是Token。
人与人之间的沟通,通过说话,而每句话便是由单词组合在一起,形成特定的语义。这里的单词便可理解成 Token。
C语言编译器相当于一个翻译,要懂两种语言——C语言和机器语言;它的工作是将C语言翻译成机器语言。首先它应该读懂C语言中的“句子”,而对整个“句子”的理解,是建立在对每个“单词”理解的基础上的,所以首先我们要把句子分成多个单词——分词。
词法分析便是将C语言的“句子”按照词法规则拆分成 Token 序列。例如:
int n1 =15;
int n2 =200;
__CONCAT(n,1); // n1
__CONCAT(n,2); // n2
常见用法:glib库中 stdint.h (sysdeps\generic):150: #define __INT64_C(c) c ## L stdint.h (sysdeps\generic):151: #define __UINT64_C(c) c ## UL 将某个常量后自动加上 L或UL,达到数据类型强制转换的目的。
个人总结: 1、##最好定义成类似:
这样A和B可以分别是另外的宏,否则编译会出错,说AB没定义。
2、改预处理不支持嵌套。
三、字符化运算符—— #@
用于创建一个字符,类似 ## ,注: 非 ANSI-C中的特性,GCC不支持,VC可以; 使用实例如下:
int main() { printf("%c\n",Dump_Char(g)); //g printf("%c\n",Dump_Char(guix)); //x 可以输入 4个长度的字符,但只输出最后一位 printf("%c\n",Dump_Char( guix )); //x 默认去除前后空格,保留中间空格 printf("%c\n",Dump_Char(guixu));//error C2015: too many characters in constant }
http://blog.csdn.net/liangwei88624/article/details/6300544
简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。其中,分隔的作用类似于空格。我们知道在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些 ##来替代空格。
举列 – 试比较下述几个宏定义的区别
A1(a1, int); /* 等价于: int name_inttype; / A2(a1, int); /_ 等价于: int a1_inttype; */ 解释: 1) 在第一个宏定义中,”name”和第一个””之间,以及第2个””和第二个 ”type”之间没有被分隔,所以预处理器会把name##type##type解释成3段:“name”、“type”、以及“type”,这中间只有“type”是在宏前面出现过 的,所以它可以被宏替换。 2) 而在第二个宏定义中,“name”和第一个“”之间也被分隔了,所以预处理器会把name##_##type##type解释成4段:“name”、“”、“type”以及“_type”,这其间,就有两个可以被宏替换了。 3) A1和A2的定义也可以如下:
<##前面随意加上一些空格>
#define A2(name, type) type name ##_ ##type ##_type
结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义。
或再比如
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary); // 这里这个语句将展开为: // typedef struct _record_type name_company_position_salary; 其他相关 – 单独的一个 # 至于单独一个#,则表示 对这个变量替换后,再加双引号引起来。比如
那么
stringify_1(linux) <==> ”linux”
所以,对于MODULE_DEVICE_TABLE
1) #define MODULE_DEVICE_TABLE(type,name)
MODULE_GENERIC_TABLE(type##_device,name)
2) #define MODULE_GENERIC_TABLE(gtype,name)
extern const struct gtype##_id mod_##gtype##_table
attribute__ ((unused, alias(*stringify(name))))
得到
MODULE_DEVICE_TABLE(usb, products)
/notes: struct usb_device_id products; */
<==> MODULE_GENERIC_TABLE(usb_device,products)
<==> extern const struct usb_device_id mod_usb_device_table
attribute** ((unused, alias(”products”)))
注意到alias attribute需要一个双引号,所以在这里使用了stringify(name)来给name加上双引号。另外,还注意到一个外部变量”__mod_usb_device_table”被alias到了本驱动专用的由用户自定义的变量products<usb_device_id类型>。
http://lxd6366.blog.163.com/blog/static/245542522013310103929882/
1、# (stringizing)字符串化操作符
作用:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。其只能用于有传入参数的宏定 义中,且必须置于宏定义体中的参数名前。
如:
当使用该宏定义时:
example(abc); 在编译时将会展开成:printf("the input string is:\t%s\n","abc"); string str=example1(abc); 将会展成:string str="abc"; *注意: 对空格的处理: a、忽略传入参数名前面和后面的空格。 如:str=example1( abc ); 将会被扩展成 str="abc"; b、当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串之间以一个空格连接,忽略剩余空格。 如:str=exapme( abc def); 将会被扩展成 str="abc def"; 2、## (token-pasting)符号连接操作符 作用:将宏定义的多个形参转换成一个实际参数名。 如:
int num9=9;
使用:
int num=exampleNum(9); 将会扩展成 int num=num9;
注意:
a、当用##连接形参时,##前后的空格可有可无。
如:#define exampleNum(n) num ## n 相当于 #define exampleNum(n) num##n
b、连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义。
另:如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开。
#define STRCPY(a, b) strcpy(a ## _p, #b)
int main()
{
char var1_p[20];
char var2_p[30];
strcpy(var1_p, "aaaa");
strcpy(var2_p, "bbbb");
STRCPY(var1, var2);
STRCPY(var2, var1);
printf("var1 = %s\n", var1_p);
printf("var2 = %s\n", var2_p);
return 0;
STRCPY(STRCPY(var1,var2),var2);
//这里是否会展开为: strcpy(strcpy(var1_p,"var2")_p,"var2“)?答案是否定的:
//展开结果将是: strcpy(STRCPY(var1,var2)_p,"var2")
//## 阻止了参数的宏展开!如果宏定义里没有用到 # 和 ##, 宏将会完全展开
}
3、#@ (charizing)字符化操作符
作用:将传入单字符参数名转换成字符,以一对单引用括起来。
如:
#define makechar(x) #@x
a = makechar(b); 展开后变成了: a= 'b'; 4、\ 续行操作符
当定义的宏不能用一行表达完整时,可以用""表示下一行继续此宏的定义。注意\前留空格
参考网址:http://blog.csdn.net/xdsoft365/archive/2010/09/28/5911596.aspx
http://blog.csdn.net/leng_que/archive/2010/11/06/5991697.aspx
http://yuxu9710108.blog.163.com/blog/static/237515342010629103421876/
http://blog.csdn.net/billpig/archive/2010/11/27/6039611.aspx
http://hbprotoss.github.io/posts/cyu-yan-hong-de-te-shu-yong-fa-he-ji-ge-keng.html
C语言宏的特殊用法和几个坑 2 years ago | Source Tags : C Macro 总结一下C语言中宏的一些特殊用法和几个容易踩的坑。由于本文主要参考GCC文档,某些细节(如宏参数中的空格是否处理之类)在别的编译器可能有细微差别,请参考相应文档。
宏基础 宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见。基本用法如下:
预处理阶段,foo = (char ) malloc (BUFFER_SIZE);会被替换成foo = (char ) malloc (1024);
宏体换行需要在行末加反斜杠\
2, \
3
预处理阶段int x[] = { NUMBERS };会被扩展成int x[] = { 1, 2, 3 };
宏名之后带括号的宏被认为是宏函数。用法和普通函数一样,只不过在预处理阶段,宏函数会被展开。优点是没有普通函数保存寄存器和参数传递的开销,展开后的代码有利于CPU cache的利用和指令预测,速度快。缺点是可执行代码体积大。
y = min(1, 2);会被扩展成y = ((1) < (2) ? (1) : (2));
宏特殊用法
在宏体中,如果宏参数前加个#,那么在宏体扩展的时候,宏参数会被扩展成字符串的形式。如:
do { if (EXP) \
fprintf (stderr, "Warning: " #EXP "\n"); } \
while (0)
WARN_IF (x == 0);会被扩展成:
do { if (x == 0) fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0); 这种用法可以用在assert中,如果断言失败,可以将失败的语句输出到反馈信息中
在宏体中,如果宏体所在标示符中有##,那么在宏体扩展的时候,宏参数会被直接替换到标示符中。如:
struct command { char _name; void (_function) (void); }; 在宏扩展的时候
struct command commands[] = { COMMAND (quit), COMMAND (help), ... }; 会被扩展成:
struct command commands[] = { { "quit", quit_command }, { "help", help_command }, ... }; 这样就节省了大量时间,提高效率。
几个坑
由于是纯文本替换,C预处理器不对宏体做任何语法检查,像缺个括号、少个分号神马的预处理器是不管的。这里要格外小心,由此可能引出各种奇葩的问题,一下还很难找到根源。
不仅宏体是纯文本替换,宏参数也是纯文本替换。有以下一段简单的宏,实现乘法:
MULTIPLY(1, 2)没问题,会正常展开成1 * 2。有问题的是这种表达式MULTIPLY(1+2, 3),展开后成了1+2 * 3,显然优先级错了。
在宏体中,给引用的参数加个括号就能避免这问题。
MULTIPLY(1+2, 3)就会被展开成(1+2) * (3),优先级正常了。
其实这个问题和下面要说到的某些问题都属于由于纯文本替换而导致的语义破坏问题,要格外小心。
有如下宏定义:
{ char _lim = (limit); \
while (p < lim) { \
if (_p++ != ' ') { \
p--; break; }}}
假设有如下一段代码:
if (*p != 0) SKIP_SPACES (p, lim); else ... 一编译,GCC报error: ‘else’ without a previous ‘if’。原来这个看似是一个函数的宏被展开后是一段大括号括起来的代码块,加上分号之后这个if逻辑块就结束了,所以编译器发现这个else没有对应的if。
这个问题一般用do ... while(0)的形式来解决:
do { char _lim = (limit); \
while (p < lim) { \
if (_p++ != ' ') { \
p--; break; }}} \
while (0)
展开后就成了
if (*p != 0) do ... while(0); else ... 这样就消除了分号吞噬问题。
这个技巧在Linux内核源码里很常见,比如这个置位宏#define SET_REG_BIT(reg, bit) do { (reg |= (1 << (bit))); } while (0)(位于arch/mips/include/asm/mach-pnx833x/gpio.h)
有如下宏定义:
当有如下调用时next = min (x + y, foo (z));,宏体被展开成next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));,可以看到,foo(z)被重复调用了两次,做了重复计算。更严重的是,如果foo是不可重入的(foo内修改了全局或静态变量),程序会产生逻辑错误。
所以,尽量不要在宏参数中传入函数调用。
有如下宏定义:
按前面的理解,(4 + foo)会展开成(4 + (4 + foo),然后一直展开下去,直至内存耗尽。但是,预处理器采取的策略是只展开一次。也就是说,foo只会展开成(4 + foo),而展开之后foo的含义就要根据上下文来确定了。
对于以下的交叉引用,宏体也只会展开一次。
x展开成(4 + y) -> (4 + (2 * x)),y展开成(2 * x) -> (2 * (4 + y))。
注意,这是极不推荐的写法,程序可读性极差。
宏参数中若包含另外的宏,那么宏参数在被代入到宏体之前会做一次完全的展开,除非宏体中含有#或##。
有如下宏定义:
AFTERX(BUFSIZE)会被展开成X_BUFSIZE。因为宏体中含有##,宏参数直接代入宏体。 XAFTERX(BUFSIZE)会被展开成X_1024。因为XAFTERX(x)的宏体是AFTERX(x),并没有#或##,所以BUFSIZE在代入前会被完全展开成1024,然后才代入宏体,变成X_1024。 -EOF-
参考资料:
http://blog.sina.com.cn/s/blog_7d9463b20100xp5t.html
C语言宏定义##连接符和#符的使用 . (2011-09-06 20:37:41)转载▼ 标签: 杂谈 分类: C语言
简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。其中,分隔的作用类似于空格。我们知道在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些 ##来替代空格。
举列 – 试比较下述几个宏定义的区别
A1(a1, int);
A2(a1, int);
解释:
1) 在第一个宏定义中,”name”和第一个””之间,以及第2个””和第二个 ”type”之间没有被分隔,所以预处理器会把name_##type##type解释成3段:“name”、“type”、以及“type”,这中间只有“type”是在宏前面出现过
的,所以它可以被宏替换。
2) 而在第二个宏定义中,“name”和第一个“”之间也被分隔了,所以预处理器会把name##_##type##type解释成4段:“name”、“”、“type”以及“_type”,这其间,就有两个可以被宏替换了。
3) A1和A2的定义也可以如下:
<##前面随意加上一些空格>
#define A2(name, type) type name ##_ ##type ##_type
结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义。
或再比如
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary); // 这里这个语句将展开为: // typedef struct _record_type name_company_position_salary; 其他相关 – 单独的一个 # 至于单独一个#,则表示 对这个变量替换后,再加双引号引起来。比如
那么
__stringify_1(linux) <==> ”linux”
所以,对于MODULE_DEVICE_TABLE
1) #define MODULE_DEVICE_TABLE(type,name)
MODULE_GENERIC_TABLE(type##_device,name)
2) #define MODULE_GENERIC_TABLE(gtype,name)
extern const struct gtype##_id mod_##gtype##_table
attribute ((unused, alias(stringify(name))))
得到
MODULE_DEVICE_TABLE(usb, products)
<==> MODULE_GENERIC_TABLE(usb_device,products)
<==> extern const struct usb_device_id mod_usb_device_table
__attribute ((unused, alias(”products”)))
注意到alias attribute需要一个双引号,所以在这里使用了stringify(name)来给name加上双引号。另外,还注意到一个外部变量”mod_usb_device_table”被alias到了本驱动专用的由用户自定义的变量products<usb_device_id类型>。
转载自http://blog.csdn.net/liangwei88624/article/details/6300544
http://onevcat.com/2014/01/black-magic-in-macro/
宏定义的黑魔法 - 宏菜鸟起飞手册
Happy define :)
宏定义在C系开发中可以说占有举足轻重的作用。底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步难行。而在更高层级进行开发时,我们会将更多的重心放在业务逻辑上,似乎对宏的使用和依赖并不多。但是使用宏定义的好处是不言自明的,在节省工作量的同时,代码可读性大大增加。如果想成为一个能写出漂亮优雅代码的开发者,宏定义绝对是必不可少的技能(虽然宏本身可能并不漂亮优雅XD)。但是因为宏定义对于很多人来说,并不像业务逻辑那样是每天会接触的东西。即使是能偶尔使用到一些宏,也更多的仅仅只停留在使用的层级,却并不会去探寻背后发生的事情。有一些开发者确实也有探寻的动力和意愿,但却在点开一个定义之后发现还有宏定义中还有其他无数定义,再加上满屏幕都是不同于平时的代码,既看不懂又不变色,于是乎心生烦恼,怒而回退。本文希望通过循序渐进的方式,通过几个例子来表述C系语言宏定义世界中的一些基本规则和技巧,从0开始,希望最后能让大家至少能看懂和还原一些相对复杂的宏。考虑到我自己现在objc使用的比较多,这个站点的读者应该也大多是使用objc的,所以有部分例子是选自objc,但是本文的大部分内容将是C系语言通用。
入门 如果您完全不知道宏是什么的话,可以先来热个身。很多人在介绍宏的时候会说,宏嘛很简单,就是简单的查找替换嘛。嗯,只说对了的一半。C中的宏分为两类,对象宏(object-like macro)和函数宏(function-like macro)。对于对象宏来说确实相对简单,但却也不是那么简单的查找替换。对象宏一般用来定义一些常数,举个例子:
//This defines PI
double r = 10.0;
double circlePerimeter = 2 * M_PI * r;
// => double circlePerimeter = 2 * 3.14159265358979323846264338327950288 * r;
printf("Pi is %0.7f",M_PI);
//Pi is 3.1415927
那么让我们开始看看另一类宏吧。函数宏顾名思义,就是行为类似函数,可以接受参数的宏。具体来说,在定义的时候,如果我们在宏名字后面跟上一对括号的话,这个宏就变成了函数宏。从最简单的例子开始,比如下面这个函数宏
//A simple function-like macro
NSString *name = @"Macro Rookie";
NSLog(@"Hello %@",SELF(name));
// => NSLog(@"Hello %@",name);
// => Hello Macro Rookie
这个宏做的事情是,在编译时如果遇到SELF,并且后面带括号,并且括号中的参数个数与定义的相符,那么就将括号中的参数换到定义的内容里去,然后替换掉原来的内容。 具体到这段代码中,SELF接受了一个name,然后将整个SELF(name)用name替换掉。嗯..似乎很简单很没用,身经百战阅码无数的你一定会认为这个宏是写出来卖萌的。那么接受多个参数的宏肯定也不在话下了,例如这样的:
printf("%d",PLUS(3,2));
// => printf("%d",3 + 2);
// => 5
相比对象宏来说,函数宏要复杂一些,但是看起来也相当简单吧?嗯,那么现在热身结束,让我们正式开启宏的大门吧。
宏的世界,小有乾坤 因为宏展开其实是编辑器的预处理,因此它可以在更高层级上控制程序源码本身和编译流程。而正是这个特点,赋予了宏很强大的功能和灵活度。但是凡事都有两面性,在获取灵活的背后,是以需要大量时间投入以对各种边界情况进行考虑来作为代价的。可能这么说并不是很能让人理解,但是大部分宏(特别是函数宏)背后都有一些自己的故事,挖掘这些故事和设计的思想会是一件很有意思的事情。另外,我一直相信在实践中学习才是真正掌握知识的唯一途径,虽然可能正在看这篇博文的您可能最初并不是打算亲自动手写一些宏,但是这我们不妨开始动手从实际的书写和犯错中进行学习和挖掘,因为只有肌肉记忆和大脑记忆协同起来,才能说达到掌握的水准。可以说,写宏和用宏的过程,一定是在在犯错中学习和深入思考的过程,我们接下来要做的,就是重现这一系列过程从而提高进步。
第一个题目是,让我们一起来实现一个MIN宏吧:实现一个函数宏,给定两个数字输入,将其替换为较小的那个数。比如MIN(1,2)出来的值是1。嗯哼,simple enough?定义宏,写好名字,两个输入,然后换成比较取值。比较取值嘛,任何一本入门级别的C程序设计上都会有讲啊,于是我们可以很快写出我们的第一个版本:
//Version 1.0
Try一下
int a = MIN(1,2);
// => int a = 1 < 2 ? 1 : 2;
printf("%d",a);
// => 1
输出正确,打包发布!
潇洒走一回
但是在实际使用中,我们很快就遇到了这样的情况
int a = 2 * MIN(3, 4);
printf("%d",a);
// => 4
看起来似乎不可思议,但是我们将宏展开就知道发生什么了
int a = 2 * MIN(3, 4);
// => int a = 2 * 3 < 4 ? 3 : 4;
// => int a = 6 < 4 ? 3 : 4;
// => int a = 4;
嘛,写程序这个东西,bug出来了,原因知道了,事后大家就都是诸葛亮了。因为小于和比较符号的优先级是较低的,所以乘法先被运算了,修正非常简单嘛,加括号就好了。
//Version 2.0
这次2 * MIN(3, 4)这样的式子就轻松愉快地拿下了。经过了这次修改,我们对自己的宏信心大增了...直到,某一天一个怒气冲冲的同事跑来摔键盘,然后给出了一个这样的例子:
int a = MIN(3, 4 < 5 ? 4 : 5);
printf("%d",a);
// => 4
简单的相比较三个数字并找到最小的一个而已,要怪就怪你没有提供三个数字比大小的宏,可怜的同事只好自己实现4和5的比较。在你开始着手解决这个问题的时候,你首先想到的也许是既然都是求最小值,那写成MIN(3, MIN(4, 5))是不是也可以。于是你就随手这样一改,发现结果变成了3,正是你想要的..接下来,开始怀疑之前自己是不是看错结果了,改回原样,一个4赫然出现在屏幕上。你终于意识到事情并不是你想像中那样简单,于是还是回到最原始直接的手段,展开宏。
int a = MIN(3, 4 < 5 ? 4 : 5);
// => int a = (3 < 4 < 5 ? 4 : 5 ? 3 : 4 < 5 ? 4 : 5); //希望你还记得运算符优先级
// => int a = ((3 < (4 < 5 ? 4 : 5) ? 3 : 4) < 5 ? 4 : 5); //为了您不太纠结,我给这个式子加上了括号
// => int a = ((3 < 4 ? 3 : 4) < 5 ? 4 : 5)
// => int a = (3 < 5 ? 4 : 5)
// => int a = 4
找到问题所在了,由于展开时连接符号和被展开式子中的运算符号优先级相同,导致了计算顺序发生了变化,实质上和我们的1.0版遇到的问题是差不多的,还是考虑不周。那么就再严格一点吧,3.0版!
//Version 3.0
至于为什么2.0版本中的MIN(3, MIN(4, 5))没有出问题,可以正确使用,这里作为练习,大家可以试着自己展开一下,来看看发生了什么。
经过两次悲剧,你现在对这个简单的宏充满了疑惑。于是你跑了无数的测试用例而且它们都通过了,我们似乎彻底解决了括号问题,你也认为从此这个宏就妥妥儿的哦了。不过如果你真的这么想,那你就图样图森破了。生活总是残酷的,该来的bug也一定是会来的。不出意外地,在一个雾霾阴沉的下午,我们又收到了一个出问题的例子。
float a = 1.0f;
float b = MIN(a++, 1.5f);
printf("a=%f, b=%f",a,b);
// => a=3.000000, b=2.000000
拿到这个出问题的例子你的第一反应可能和我一样,这TM的谁这么二货还在比较的时候搞++,这简直乱套了!但是这样的人就是会存在,这样的事就是会发生,你也不能说人家逻辑有错误。a是1,a++表示先使用a的值进行计算,然后再加1。那么其实这个式子想要计算的是取a和b的最小值,然后a等于a加1:所以正确的输出a为2,b为1才对!嘛,满眼都是泪,让我们这些久经摧残的程序员淡定地展开这个式子,来看看这次又发生了些什么吧:
float a = 1.0f;
float b = MIN(a++, 1.5f);
// => float b = ((a++) < (1.5f) ? (a++) : (1.5f))
其实只要展开一步就很明白了,在比较a++和1.5f的时候,先取1和1.5比较,然后a自增1。接下来条件比较得到真以后又触发了一次a++,此时a已经是2,于是b得到2,最后a再次自增后值为3。出错的根源就在于我们预想的是a++只执行一次,但是由于宏展开导致了a++被多执行了,改变了预想的逻辑。解决这个问题并不是一件很简单的事情,使用的方式也很巧妙。我们需要用到一个GNU C的赋值扩展,即使用({...})的形式。这种形式的语句可以类似很多脚本语言,在顺次执行之后,会将最后一次的表达式的赋值作为返回。举个简单的例子,下面的代码执行完毕后a的值为3,而且b和c只存在于大括号限定的代码域中
int a = ({
int b = 1;
int c = 2;
b + c;
});
// => a is 3
有了这个扩展,我们就能做到之前很多做不到的事情了。比如彻底解决MIN宏定义的问题,而也正是GNU C中MIN的标准写法
//GNUC MIN
这里定义了三个语句,分别以输入的类型申明了a和b,并使用输入为其赋值,接下来做一个简单的条件比较,得到a和b中的较小值,并使用赋值扩展将结果作为返回。这样的实现保证了不改变原来的逻辑,先进行一次赋值,也避免了括号优先级的问题,可以说是一个比较好的解决方案了。如果编译环境支持GNU C的这个扩展,那么毫无疑问我们应该采用这种方式来书写我们的MIN宏,如果不支持这个环境扩展,那我们只有人为地规定参数不带运算或者函数调用,以避免出错。
关于MIN我们讨论已经够多了,但是其实还存留一个悬疑的地方。如果在同一个scope内已经有a或者b的定义的话(虽然一般来说不会出现这种悲剧的命名,不过谁知道呢),这个宏可能出现问题。在申明后赋值将因为定义重复而无法被初始化,导致宏的行为不可预知。如果您有兴趣,不妨自己动手试试看结果会是什么。Apple在Clang中彻底解决了这个问题,我们把Xcode打开随便建一个新工程,在代码中输入MIN(1,1),然后Cmd+点击即可找到clang中 MIN的写法。为了方便说明,我直接把相关的部分抄录如下:
//CLANG MIN
似乎有点长,看起来也很吃力。我们先美化一下这宏,首先是最后那个__NSMIN_IMPL**内容实在是太长了。我们知道代码的话是可以插入换行而不影响含义的,宏是否也可以呢?答案是肯定的,只不过我们不能使用一个单一的回车来完成,而必须在回车前加上一个反斜杠\。改写一下,为其加上换行好看些:
__typeof**(B) **NSX_PASTE**(**b,L) = (B); \
(__NSX_PASTE**(**a,L) < __NSX_PASTE**(**b,L)) ? __NSX_PASTE**(**a,L) : __NSX_PASTE**(**b,L); \
})
但可以看出MIN一共由三个宏定义组合而成。第一个NSX_PASTE里出现的两个连着的井号##在宏中是一个特殊符号,它表示将两个参数连接起来这种运算。注意函数宏必须是有意义的运算,因此你不能直接写AB来连接两个参数,而需要写成例子中的A##B。宏中还有一切其他的自成一脉的运算符号,我们稍后还会介绍几个。接下来是我们调用的两个参数的MIN,它做的事是调用了另一个三个参数的宏NSMIN_IMPL,其中前两个参数就是我们的输入,而第三个COUNTER我们似乎不认识,也不知道其从何而来。其实COUNTER**是一个预定义的宏,这个值在编译过程中将从0开始计数,每次被调用时加1。因为唯一性,所以很多时候被用来构造独立的变量名称。有了上面的基础,再来看最后的实现宏就很简单了。整体思路和前面的实现和之前的GNUC MIN是一样的,区别在于为变量名a和b添加了一个计数后缀,这样大大避免了变量名相同而导致问题的可能性(当然如果你执拗地把变量叫做a9527并且出问题了的话,就只能说不作死就不会死了)。
花了好多功夫,我们终于把一个简单的MIN宏彻底搞清楚了。宏就是这样一类东西,简单的表面之下隐藏了很多玄机,可谓小有乾坤。作为练习大家可以自己尝试一下实现一个SQUARE(A),给一个数字输入,输出它的平方的宏。虽然一般这个计算现在都是用inline来做了,但是通过和MIN类似的思路我们是可以很好地实现它的,动手试一试吧 :)
Log,永恒的主题 Log人人爱,它为我们指明前进方向,它为我们抓虫提供帮助。在objc中,我们最多使用的log方法就是NSLog输出信息到控制台了,但是NSLog的标准输出可谓残废,有用信息完全不够,比如下面这段代码:
NSArray *array = @[@"Hello", @"My", @"Macro"];
NSLog (@"The array is %@", array);
打印到控制台里的结果是类似这样的
2014-01-20 11:22:11.835 TestProject[23061:70b] The array is (
Hello,
My,
Macro
)
我们在输出的时候关心什么?除了结果以外,很多情况下我们会对这行log的所在的文件位置方法什么的会比较关心。在每次NSLog里都手动加上方法名字和位置信息什么的无疑是个笨办法,而如果一个工程里已经有很多NSLog的调用了,一个一个手动去改的话无疑也是噩梦。我们通过宏,可以很简单地完成对NSLog原生行为的改进,优雅,高效。只需要在预编译的pch文件中加上
//A better version of NSLog
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:**FILE**] lastPathComponent] UTF8String], \
**LINE**, **func**); \
(NSLog)((format), ##**VA_ARGS**); \
fprintf(stderr, "-------\n"); \
} while (0)
嘛,这是我们到现在为止见到的最长的一个宏了吧...没关系,一点一点来分析就好。首先是定义部分,第2行的NSLog(format, ...)。我们看到的是一个函数宏,但是它的参数比较奇怪,第二个参数是...,在宏定义(其实也包括函数定义)的时候,写为...的参数被叫做可变参数(variadic)。可变参数的个数不做限定。在这个宏定义中,除了第一个参数format将被单独处理外,接下来输入的参数将作为整体一并看待。回想一下NSLog的用法,我们在使用NSLog时,往往是先给一个format字符串作为第一个参数,然后根据定义的格式在后面的参数里跟上写要输出的变量之类的。这里第一个格式化字符串即对应宏里的format,后面的变量全部映射为...作为整体处理。
接下来宏的内容部分。上来就是一个下马威,我们遇到了一个do while语句...想想看你上次使用do while是什么时候吧?也许是C程序设计课的大作业?或者是某次早已被遗忘的算法面试上?总之虽然大家都是明白这个语句的,但是实际中可能用到它的机会少之又少。乍一看似乎这个do while什么都没做,因为while是0,所以do肯定只会被执行一次。那么它存在的意义是什么呢,我们是不是可以直接简化一下这个宏,把它给去掉,变成这个样子呢?
//A wrong version of NSLog
[[[NSString stringWithUTF8String:**FILE**] lastPathComponent] UTF8String], \
**LINE**, **func**); \
(NSLog)((format), ##**VA_ARGS**); \
fprintf(stderr, "-------\n");
答案当然是否定的,也许简单的测试里你没有遇到问题,但是在生产环境中这个宏显然悲剧了。考虑下面的常见情况
if (errorHappend)
NSLog(@"Oops, error happened");
展开以后将会变成
if (errorHappend)
fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func);
(NSLog)((format), ##VA_ARGS); //I will expand this later
fprintf(stderr, "-------\n");
注意..C系语言可不是靠缩进来控制代码块和逻辑关系的。所以说如果使用这个宏的人没有在条件判断后加大括号的话,你的宏就会一直调用真正的NSLog输出东西,这显然不是我们想要的逻辑。当然在这里还是需要重新批评一下认为if后的单条执行语句不加大括号也没问题的同学,这是陋习,无需理由,请改正。不论是不是一条语句,也不论是if后还是else后,都加上大括号,是对别人和自己的一种尊重。
好了知道我们的宏是如何失效的,也就知道了修改的方法。作为宏的开发者,应该力求使用者在最大限度的情况下也不会出错,于是我们想到直接用一对大括号把宏内容括起来,大概就万事大吉了?像这样:
//Another wrong version of NSLog
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:**FILE**] lastPathComponent] UTF8String], \
**LINE**, **func**); \
(NSLog)((format), ##**VA_ARGS**); \
fprintf(stderr, "-------\n"); \
}
展开刚才的那个式子,结果是
//I am sorry if you don't like { in the same like. But I am a fan of this style :P
if (errorHappend) {
fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func);
(NSLog)((format), ##VA_ARGS);
fprintf(stderr, "-------\n");
};
编译,执行,正确!因为用大括号标识代码块是不会嫌多的,所以这样一来的话我们的宏在不论if后面有没有大括号的情况下都能工作了!这么看来,前面例子中的do while果然是多余的?于是我们又可以愉快地发布了?如果你够细心的话,可能已经发现问题了,那就是上面最后的一个分号。虽然编译运行测试没什么问题,但是始终稍微有些刺眼有木有?没错,因为我们在写NSLog本身的时候,是将其当作一条语句来处理的,后面跟了一个分号,在宏展开后,这个分号就如同噩梦一般的多出来了。什么,你还没看出哪儿有问题?试试看展开这个例子吧:
if (errorHappend)
NSLog(@"Oops, error happened");
else
//Yep, no error, I am happy~ :)
No! I am not haapy at all! 因为编译错误了!实际上这个宏展开以后变成了这个样子:
if (errorHappend) {
fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func);
(NSLog)((format), ##VA_ARGS);
fprintf(stderr, "-------\n");
}; else {
//Yep, no error, I am happy~ :)
}
因为else前面多了一个分号,导致了编译错误,很恼火..要是写代码的人乖乖写大括号不就啥事儿没有了么?但是我们还是有巧妙的解决方法的,那就是上面的do while。把宏的代码块添加到do中,然后之后while(0),在行为上没有任何改变,但是可以巧妙地吃掉那个悲剧的分号,使用do while的版本展开以后是这个样子的
if (errorHappend)
do {
fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func);
(NSLog)((format), ##VA_ARGS);
fprintf(stderr, "-------\n");
} while (0);
else {
//Yep, no error, I am really happy~ :)
}
这个吃掉分号的方法被大量运用在代码块宏中,几乎已经成为了标准写法。而且while(0)的好处在于,在编译的时候,编译器基本都会为你做好优化,把这部分内容去掉,最终编译的结果不会因为这个do while而导致运行效率上的差异。在终于弄明白了这个奇怪的do while之后,我们终于可以继续深入到这个宏里面了。宏本体内容的第一行没有什么值得多说的fprintf(stderr, "<%s : %d> %s\n",,简单的格式化输出而已。注意我们使用了\将这个宏分成了好几行来写,实际在最后展开时会被合并到同一行内,我们在刚才MIN最后也用到了反斜杠,希望你还能记得。接下来一行我们填写这个格式输出中的三个token,
[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func); 这里用到了三个预定义宏,和刚才的COUNTER类似,预定义宏的行为是由编译器指定的。FILE返回当前文件的绝对路径,LINE返回展开该宏时在文件中的行数,func是改宏所在scope的函数名称。我们在做Log输出时如果带上这这三个参数,便可以加快解读Log,迅速定位。关于编译器预定义的Log以及它们的一些实现机制,感兴趣的同学可以移步到gcc文档的PreDefine页面和clang的Builtin Macro进行查看。在这里我们将格式化输出的三个参数分别设定为文件名的最后一个部分(因为绝对路径太长很难看),行数,以及方法名称。
接下来是还原原始的NSLog,(NSLog)((format), ##VA_ARGS);中出现了另一个预定义的宏VA_ARGS(我们似乎已经找出规律了,前后双下杠的一般都是预定义)。VA_ARGS表示的是宏定义中的...中的所有剩余参数。我们之前说过可变参数将被统一处理,在这里展开的时候编译器会将VA_ARGS直接替换为输入中从第二个参数开始的剩余参数。另外一个悬疑点是在它前面出现了两个井号##。还记得我们上面在MIN中的两个井号么,在那里两个井号的意思是将前后两项合并,在这里做的事情比较类似,将前面的格式化字符串和后面的参数列表合并,这样我们就得到了一个完整的NSLog方法了。之后的几行相信大家自己看懂也没有问题了,最后输出一下试试看,大概看起来会是这样的。
<AppDelegate.m : 46> -[AppDelegate application:didFinishLaunchingWithOptions:]
2014-01-20 16:44:25.480 TestProject[30466:70b] The array is (
Hello,
My,
Macro
带有文件,行号和方法的输出,并且用横杠隔开了(请原谅我没有质感的设计,也许我应该画一只牛,比如这样?),debug的时候也许会轻松一些吧 :)
hello cowsay
这个Log有三个悬念点,首先是为什么我们要把format单独写出来,然后吧其他参数作为可变参数传递呢?如果我们不要那个format,而直接写成NSLog(...)会不会有问题?对于我们这里这个例子来说的话是没有变化的,但是我们需要记住的是...是可变参数列表,它可以代表一个、两个,或者是很多个参数,但同时它也能代表零个参数。如果我们在申明这个宏的时候没有指定format参数,而直接使用参数列表,那么在使用中不写参数的NSLog()也将被匹配到这个宏中,导致编译无法通过。如果你手边有Xcode,也可以看看Cocoa中真正的NSLog方法的实现,可以看到它也是接收一个格式参数和一个参数列表的形式,我们在宏里这么定义,正是为了其传入正确合适的参数,从而保证使用者可以按照原来的方式正确使用这个宏。
第二点是既然我们的可变参数可以接受任意个输入,那么在只有一个format输入,而可变参数个数为零的时候会发生什么呢?不妨展开看一看,记住##的作用是拼接前后,而现在##之后的可变参数是空:
NSLog(@"Hello");
=> do {
fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func);
(NSLog)((@"Hello"), );
fprintf(stderr, "-------\n");
} while (0);
中间的一行(NSLog)(@"Hello", );似乎是存在问题的,你一定会有疑惑,这种方式怎么可能编译通过呢?!原来大神们其实早已想到这个问题,并且进行了一点特殊的处理。这里有个特殊的规则,在逗号和VA_ARGS之间的双井号,除了拼接前后文本之外,还有一个功能,那就是如果后方文本为空,那么它会将前面一个逗号吃掉。这个特性当且仅当上面说的条件成立时才会生效,因此可以说是特例。加上这条规则后,我们就可以将刚才的式子展开为正确的(NSLog)((@"Hello"));了。
最后一个值得讨论的地方是(NSLog)((format), ##VA_ARGS);的括号使用。把看起来能去掉的括号去掉,写成NSLog(format, ##VA_ARGS);是否可以呢?在这里的话应该是没有什么大问题的,首先format不会被调用多次也不太存在误用的可能性(因为最后编译器会检查NSLog的输入是否正确)。另外你也不用担心展开以后式子里的NSLog会再次被自己展开,虽然展开式中NSLog也满足了我们的宏定义,但是宏的展开非常聪明,展开后会自身无限循环的情况,就不会再次被展开了。
作为一个您读到了这里的小奖励,附送三个debug输出rect,size和point的宏,希望您能用上(嗯..想想曾经有多少次你需要打印这些结构体的某个数字而被折磨致死,让它们玩儿蛋去吧!当然请先加油看懂它们吧)
两个实际应用的例子 当然不是说上面介绍的宏实际中不能用。它们相对简单,但是里面坑不少,所以显得很有特点,非常适合作为入门用。而实际上在日常中很多我们常用的宏并没有那么多奇怪的问题,很多时候我们按照想法去实现,再稍微注意一下上述介绍的可能存在的共通问题,一个高质量的宏就可以诞生。如果能写出一些有意义价值的宏,小了从对你的代码的使用者来说,大了从整个社区整个世界和减少碳排放来说,你都做出了相当的贡献。我们通过几个实际的例子来看看,宏是如何改变我们的生活,和写代码的习惯的吧。
先来看看这两个宏
_XCTPrimitiveAssertTrue(expression, ## format)
({ \ @try { \ BOOL _evaluatedExpression = !!(expression); \ if (!_evaluatedExpression) { \ _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 0, @#expression),format); \ } \ } \ @catch (id exception) { \ _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 1, @#expression, [exception reason]),format); \ }\ }) 如果您常年做苹果开发,却没有见过或者完全不知道XCTAssertTrue是什么的话,强烈建议补习一下测试驱动开发的相关知识,我想应该会对您之后的道路很有帮助。如果你已经很熟悉这个命令了,那我们一起开始来看看幕后发生了什么。
有了上面的基础,相信您大体上应该可以自行解读这个宏了。({...})的语法和##都很熟悉了,这里有三个值得注意的地方,在这个宏的一开始,我们后面的的参数是format...,这其实也是可变参数的一种写法,和...与VA_ARGS配对类似,{NAME}...将于{NAME}配对使用。也就是说,在这里宏内容的format指代的其实就是定义的先对expression取了两次反?我不是科班出身,但是我还能依稀记得这在大学程序课上讲过,两次取反的操作可以确保结果是BOOL值,这在objc中还是比较重要的(关于objc中BOOL的讨论已经有很多,如果您还没能分清BOOL, bool和Boolean,可以参看NSHisper的这篇文章)。然后就是@#expression这个式子。我们接触过双井号##,而这里我们看到的操作符是单井号#,注意井号前面的@是objc的编译符号,不属于宏操作的对象。单个井号的作用是字符串化,简单来说就是将替换后在两头加上"",转为一个C字符串。这里使用@然后紧跟#expression,出来后就是一个内容是expression的内容的NSString。然后这个NSString再作为参数传递给_XCTRegisterFailure和_XCTFailureDescription等,继续进行展开,这些是后话。简单一瞥,我们大概就可以想象宏帮助我们省了多少事儿了,如果各位看官要是写个断言还要来个十多行的话,想象都会疯掉的吧。
另外一个例子,找了人民群众喜闻乐见的ReactiveCocoa(RAC)中的一个宏定义。对于RAC不熟悉或者没听过的朋友,可以简单地看看Limboy的一系列相关博文(搜索ReactiveCocoa),介绍的很棒。如果觉得“哇哦这个好酷我很想学”的话,不妨可以跟随raywenderlich上这个系列的教程做一些实践,里面简单地用到了RAC,但是都已经包含了RAC的基本用法了。RAC中有几个很重要的宏,它们是保证RAC简洁好用的基本,可以说要是没有这几个宏的话,是不会有人喜欢RAC的。其中RACObserve就是其中一个,它通过KVC来为对象的某个属性创建一个信号返回(如果你看不懂这句话,不要担心,这对你理解这个宏的写法和展开没有任何影响)。对于这个宏,我决定不再像上面那样展开和讲解,我会在最后把相关的宏都贴出来,大家不妨拿它练练手,看看能不能将其展开到代码的状态,并且明白其中都发生了些什么。如果你遇到什么问题或者在展开过程中有所心得,欢迎在评论里留言分享和交流 :)
好了,这篇文章已经够长了。希望在看过以后您在看到宏的时候不再发怵,而是可以很开心地说这个我会这个我会这个我也会。最终目标当然是写出漂亮高效简洁的宏,这不论对于提高生产力还是~震慑你的同事~提升自己实力都会很有帮助。
另外,在这里一定要宣传一下关注了很久的@hangcom 吴航前辈的新书《iOS应用逆向工程》。很荣幸能够在发布之前得到前辈的允许拜读了整本书,可以说看的畅快淋漓。我之前并没有越狱开发的任何基础,也对相关领域知之甚少,在这样的前提下跟随书中的教程和例子进行探索的过程可以说是十分有趣。我也得以能够用不同的眼光和高度来审视这几年所从事的iOS开发行业,获益良多。可以说《iOS应用逆向工程》是我近期所愉快阅读到的很cool的一本好书。现在这本书还在预售中,但是距离1月28日的正式发售已经很近,有兴趣的同学可以前往亚马逊或者ChinaPub的相关页面预定,相信这本书将会是iOS技术人员非常棒的春节读物。
最后是我们说好的留给大家玩的练习,我加了一点注释帮助大家稍微理解每个宏是做什么的,在文章后面留了一块试验田,大家可以随便填写玩弄。总之,加油!
//调用 RACSignal是类的名字 RACSignal *signal = RACObserve(self, currentLocation);
//以下开始是宏定义 //rac_valuesForKeyPath:observer:是方法名
[(id)(TARGET) rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]
metamacro_if_eq(1, metamacro_argcount(**VA_ARGS**))(keypath1(**VA_ARGS**))(keypath2(**VA_ARGS**))
//这个宏在取得keypath的同时在编译期间判断keypath是否存在,避免误写 //您可以先不用介意这里面的巫术..
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
//A和B是否相等,若相等则展开为后面的第一项,否则展开为后面的第二项 //eg. metamacro_if_eq(0, 0)(true)(false) => true // metamacro_if_eq(0, 1)(true)(false) => false
metamacro_concat(metamacro_if_eq, A)(B)
metamacro_concat(metamacro_if_eq0_, VALUE)
metamacro_at(20, **VA_ARGS**, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
metamacro_concat(metamacro_at, N)(**VA_ARGS**)
metamacro_concat_(A, B)
metamacro_head_(**VA_ARGS**, 0)
metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
//调用 RACSignal是类的名字 RACSignal *signal = RACObserve(self, currentLocation);
有意义的评论补充:
1.如果大家看这些宏看起来很吃力的话可以用下面这个三个宏去查看宏的内容,另外帮博主补充一点小知识,宏的展开顺序为先完全展开宏参数,再扫描宏展开后里面的宏,如此反复,有两种例外参数不会被先展开,宏内容中含有#,##这两个符号的时候。这也是下面能打印宏展开后内容的原理所在。
2.在以 “接下来是还原原始的NSLog” 开始的段落中,关于双井号 (##
) 提到 “在这里做的事情比较类似,将前面的格式化字符串和后面的参数列表合并”。虽然后文中有提到是为了在 0 个可变实参时吞掉前面的逗号,但这里 “合并” 的说法我觉得是有误 。
从 http://gcc.gnu.org/onlinedocs/... 能看出:##__VA_ARGS__
中双井号的意义是,“调用”含可变参数的函数宏时,如果完全忽略了可变参数,双井号用来告知编译器:删除(函数)宏展开语句中位于它之前的那个逗号,以避免编译错误。
3.不过打印Rect, Size, Point时可以试试:
NSLog(@"%@", NSStringFromCGRect(rect));
NSLog(@"%@", NSStringFromCGSize(size));
NSLog(@"%@", NSStringFromCGPoint(point));
English version http://www.programering.com/a/MDM2ATMwATI.html
Macro definitions can be said to occupy the role play a decisive role in the development of C system. The underlying framework since Needless to say, in order to compiler optimization and convenient, and cross platform ability, macro is used in great quantities, can be said to the bottom of the development from define will can't do anything. And developing on a higher level, we will be more focus on the business logic, the use of macro and dependence seems not much. But the use of macro definitions benefit is self-evident, the workload is saved at the same time, code readability greatly increased.
If you want to become a can write beautiful code developers, macro definition is absolutely essential skills (although macros may not beautiful elegant XD). But because the macro definition for many people, and not the business logic that would be in contact with every day. Even the occasional use of some macro, also more just stay in the use of the hierarchy, but will not seek behind what happened.
Some developers have to explore the motivation and willingness, after but points to a defined that there are macro definitions and countless other definitions, plus full screen are different from the usual code, not only cannot read and do not change color, then the heart troubles, anger and rollback. We hope that through the gradual way, through several examples to describe the C language macro definitions in the world of some of the basic rules and techniques, from the beginning of 0, we can let you can at least read and restore some relatively complicated macros. Considering that I now use objc more, this site's readers should also mostly use objc, so there are some examples are selected from the group consisting of objc, but most of the contents of this paper will be the C language.
Introduction
If you don't know what the macro is words, can come to a body heat. When the macro will be a lot of people say, macro, is very simple, is a simple search and replace. Well, only half right. The macros in C are divided into two categories, object macro (object-like macro) and the function of macro(function-like macro). For the macro is relatively simple, but it is not so simple search and replace. Object macro generally used to define some parameters, for example: //This defines PI
double r = 10.0; double circlePerimeter = 2 * M_PI * r; // => double circlePerimeter = 2 * 3.14159265358979323846264338327950288 * r;
printf("Pi is %0.7f",M_PI); //Pi is 3.1415927 So let us begin to have a look of another kind of macro. Function macros as the name suggests, is to act like functions, can accept parameters macro. Specifically, in the definition of time, if we follow in the macro name behind a pair of brackets, this macro will become a function like macro. From the beginning of the most simple example, for example, the following function macros //A simple function-like macro
NSString *name = @"Macro Rookie"; NSLog(@"Hello %@",SELF(name)); // => NSLog(@"Hello %@",name); // => Hello Macro Rookie This macro to do is, at compile time if encounter SELF, and back with brackets, and the number of parameters and definition in parentheses correspond, then the parameters in parentheses to define the contents of go, and then replace the original content. Specific to this code, SELF received a name, then the SELF (name) name replace. Uh.. seems very simple very useless, be a veteran in battle to read code countless you will think this macro is written act loving. So accept multiple parameters of the macro must also be nothing difficult, such as the:
printf("%d",PLUS(3,2)); // => printf("%d",3 + 2); // => 5 Compared with the object of macro, function macros to complex, but also looks quite simple? Well, now the warm-up the end, let us formally opens the great gate.
The macro world, a small universe
Because the macro expansion is pretreatment editor, so it can control the program source code and compile process at a higher level. And it is this characteristic, gives the macro very powerful function and flexibility. But every coin has two sides, behind the acquisition of flexible, is to need lots of time to devote to various boundary conditions were considered as the price. That might not be very understandable, but most macro (especially a function like macro) there are some of their stories, mining these stories and design idea will be a very interesting things. In addition, I always believe that learning in practice is the only way to truly grasp the knowledge, although it might be watching this blog you may initially not intended to dear automatic handwritten macros, but that we might as well start learning and mining from the actual writing and making mistakes, because only the muscle memory and memory Co it can be said to achieve mastery level. Can say, write macros and macro process, must be in the process of learning and thinking deeply in the wrong, we are going to do, is to reproduce a series of process and improving progress.
The first question is, let us work together to achieve a MIN macro: the implementation of a function like macro, given two digital input, to replace the smaller the number. For example, MIN (1,2) 1 is out the value of the. Uh huh, simple enough? Define macros, write the name, two input, and then into the comparative value. Comparison value, C program design any of the entry level will have to speak it, so we can quickly write our first version: //Version 1.0
Try int a = MIN(1,2); // => int a = 1 <2 ? 1 : 2; printf("%d",a); // => 1 The output is correct, publish!
Handsome go back But in actual use, we soon encountered such a situation int a = 2 * MIN(3, 4); printf("%d",a); // => 4 It seems incredible, but we will know what the macro expansion int a = 2 * MIN(3, 4); // => int a = 2 * 3 <4 ? 3 : 4; // => int a = 6 <4 ? 3 : 4; // => int a = 4; Well, write a program that, bug out, cause I know, after everyone is Zhu Geliang. Because of less than and more symbolic priority is lower, so the multiplication is the operation, correction is very simple, parentheses as well.
//Version 2.0
The 2 * MIN (3, 4) this formula is relaxed and happy with the. After this modification, we add to their macro confidence... Until, one day a colleague came to be ablaze with anger broke the keyboard, and then gives an example of this: int a = MIN(3, 4 <5 ? 4 : 5); printf("%d",a); // => 4 Compared with the three digital simple and to find a minimal, blame you do not provide three numbers than the size of the macro, poor colleagues had to achieve between 4 and 5. When you begin to solve this problem, your first thought might be that is the minimum value, that is written in MIN (3, MIN (4, 5)) Is it right? Can also. So you just such a change, found the results into 3, is what you want. Next, begin to doubt yourself before Is it right? Wrong results, change back, a 4 popped up on the screen. You finally realize that things are not as simple as you think, and then returned to the original direct means, expand macros. int a = MIN(3, 4 <5 ? 4 : 5); // => int a = (3 <4 <5 ? 4 : 5 ? 3 : 4 <5 ? 4 : 5); //I hope you remember operator precedence // => int a = ((3 <(4 <5 ? 4 : 5) ? 3 : 4) <5 ? 4 : 5); //In order to you don't struggle too much, I give the formula plus bracket // => int a = ((3 <4 ? 3 : 4) <5 ? 4 : 5) // => int a = (3 <5 ? 4 : 5) // => int a = 4 Found the problem, because the expansion connection symbol and be expanded operator priority formula of the same, causes the calculation order has changed, the essence and our 1 edition problem is about the same, or consideration not week. There would be strictly a bit, 3 edition! //Version 3.0
As for why the 2 version of the MIN (3, MIN (4, 5)) no problem, can be properly used, here as an exercise, you can try their own out of me, to have a look what happened. After two times of tragedy, you are now on the simple macro full of doubts. So you run the test case countless and they have passed, we seem to be completely solved the problem of brackets, you also think that from this macro will be tall oh. But if you really want it, you're drawing Tucson broken. Life is cruel, the bug must also be. Not surprisingly, in a haze gloomy afternoon, we got a problem example. float a = 1.0f; float b = MIN(a++, 1.5f); printf("a=%f, b=%f",a,b); // => a=3.000000, b=2.000000 Get this problem examples of your first reaction may be like me, when comparing the TM who's the idiot still make + +, it went wrong! But such people will exist, such a thing happens, you can't say that others logic errors. A is 1, a++ said the first use of a values were calculated, then add 1. Then the formula to calculate the minimum value of a and B, then a equals a plus 1: so the correct output a is 2, B is 1 to! Well, eyeful tears, let us these time by programmers Danding to expand this formula, to have a look this time what happened again: float a = 1.0f; float b = MIN(a++, 1.5f); // => float b = ((a++) <(1.5f) ? (a++) : (1.5f)) In fact, as long as the expansion step is clear, when comparing a++ and 1.5f, take 1 and 1.5 comparison, then a is incremented by 1. The following conditions are true then trigger a a++, then a is 2, and B 2, finally a again since the increased value of 3. Error source is that we expected the a++ executes only once, but because the macro expansion led to a++ being multiple execution, change the expected logic. To solve this problem is not a very simple thing, way of use is also very clever. We need an assignment to a GNU C extension, namely the use of ({...}) form. This form of statement is similar to many scripting languages, in order to perform the assignment expression, will last as return. Cite a simple example, the following code is executed after the a value is 3, and the B and C only exist code field in braces in Limited int a = ({ int b = 1; int c = 2; b + c; }); // => a is 3 With this extension, we can do many things not to do. For example, completely solve the MIN macro to define the problem, and it is the standard GNU C MIN //GNUC MIN
It defines three statement, the input type that a and b, and use the input for the next assignment, compared to a simple condition, obtain smaller a and b values, and use the assignment expansion will result as the return. Such an implementation which does not change the original logic, to conduct a valuation, but also to avoid the brackets priority issues, can be said to be a good solution. If the extended compiler environment to support the GNU C, so there is no doubt that we should adopt this way to write our MIN macro, if does not support this environment expansion, then we only artificially specified parameters without operation or function call, in order to avoid mistakes.
About MIN we discuss enough, but still remain a mystery. If in the same scope has defined **a or __b words (although nomenclature, generally does not appear in this tragic but who knows), this macro problems may arise. In a statement after the assignment will be because the definition repeatedly and not be initialized, lead to macro behavior unpredictable. If you are interested, may wish to have a try yourself what results will be. Apple in Clang completely solve the problem, we turn on Xcode to build a new project, enter MIN in the code (1,1), and then click clang MIN Cmd+ can be found in writing. In order to facilitate the description, I direct the notes related as follows: //CLANG MIN
It seems a little long, looks very difficult. We first beautifies this macro, first of all is the final __NSMIN_IMPL** content is much too long. We know the code word that can be inserted into a newline without affecting the meaning, if macros can be? The answer is yes, but we can not use a single enter to finish, but must add the last backslash in front. To paraphrase, to add newlines look better:
__typeof**(B) **NSX_PASTE**(**b,L) = (B); \
(__NSX_PASTE**(**a,L) <__NSX_PASTE**(**b,L)) ? __NSX_PASTE**(**a,L) : __NSX_PASTE**(**b,L); \
})
But we can see that the MIN is composed of three macro assembly. The first NSX_PASTE occurred in the two connected with the well No. ## in a macro is a special symbol, it will connect the two parameter calculation. Note that a function like macro must be meaningful operation, so you can't write directly to AB to connect the two parameters, and the need to write an example of a A##B. Macro and everything from some other operation symbols, we later introduce several. Next is the two parameter we call MIN, what it does is to call another three parameters of the macro NSMIN_IMPL, of which the first two parameters are input, and the third COUNTER we seem not to know, don't know where it came from. In fact, COUNTER** is a predefined macros, this value to start counting from 0 in the compilation process, each time it is called the plus 1. Because of uniqueness, so a lot of time were used to construct the independent variable name. With the above basis, come see implement macro finally is very simple. The realization of the overall train of thought and the front and GNUC before MIN is the same, the difference is adding a count suffix for variable names a and b, thus avoiding the variable name the same problems due to the possibility (of course if you obstinately to variable called a9527 and wrong words, can only say no death will not die).
Spend a lot of effort, we finally have a simple MIN macro completely understood. A macro is such a kind of things, the simple surface is hiding a lot of mystery, is a small universe. As an exercise you can try their own implementation of a SQUARE (A), to a digital input, output and its square macros. Although the calculations are now using inline to do, but by thinking and MIN like we can well realize it, try it:)
Log, Eternal theme Log everyone love, it is our direction, it help us to catch the worm. In objc, log method we use at most is the NSLog output information to the console, but the standard output NSLog is disabled, the useful information is not enough, for example, the following code: NSArray *array = @[@"Hello", @"My", @"Macro"]; NSLog (@"The array is %@", array); Print to console the results like this 2014-01-20 11:22:11.835 TestProject[23061:70b] The array is ( Hello, My, Macro ) What we care about in the output when? Apart from the result, the file location method in many cases we will be on the line of log is more concerned about what will be. In each NSLog are manual plus the method name and location information what is a stupid way, and if there are a lot of NSLog calls a project, a manual to change no doubt is a nightmare. We through the macro, can easily complete the improvement of native NSLog behavior, elegant, efficient. In the pre compiled PCH files and need only //A better version of NSLog
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:**FILE**] lastPathComponent] UTF8String], \
**LINE**, **func**); \
(NSLog)((format), ##**VA_ARGS**); \
fprintf(stderr, "-------\n"); \
} while (0)
Well, this is a macro longest we now see... Never mind, one point one points to analysis. The first part is the definition, second lines of NSLog(format, ...). What we see is a function like macro, but its parameters is strange, the second parameter is..., in the macro definition (in fact also includes the function definition) when writing, parameter called variable parameters for..(variadic). The number of variable parameters do not limit. In the macro definition, in addition to the first parameter format will be treated separately, then the input parameters as a whole will be see. Recall that the NSLog usage, we use NSLog, is often the first to give a format string as the first parameter, then according to the definition of the format in the back of the parameter to keep up with the write to output variables such as. Here the first format string that corresponds to the macro in the format, behind the variable mapping as... As a whole processing.
The macro part. It is a run, we encountered a do while statement... Just think what was the last time you use the do while? Maybe is a C program design course of the operation? Or a long forgotten algorithm interview? Anyway, although everyone is aware of this statement, but in practice it may use less. At first glance it seems that this do while what didn't do, because while is 0, so the do must only be performed once. Then what is the meaning of its existence, we Is it right? Can simplify this macro, give it to get rid of, be like this? //A wrong version of NSLog
[[[NSString stringWithUTF8String:**FILE**] lastPathComponent] UTF8String], \
**LINE**, **func**); \
(NSLog)((format), ##**VA_ARGS**); \
fprintf(stderr, "-------\n");
Of course the answer is no, maybe a simple test, you don't have a problem, but in a production environment this macro apparently tragedy. Considering the common situation of the following
if (errorHappend) NSLog(@"Oops, error happened"); Expansion will become
if (errorHappend) fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func); (NSLog)((format), ##VA_ARGS); //I will expand this later fprintf(stderr, "-------\n");
Note that the..C language can not by shrinkage in control of a code block and logical relationship. So if you use this macro did not increase the brackets in the condition after the judgment, you will always call the NSLog output macro something real, this is not what we want logic. Of course, here still need to criticisms that if after the single executable statement does not increase the brackets have no problem students, this is the bad habits, does not need the reason, please correct. No matter Is it right? A statement, whether it's if or else, with braces, is a kind of respect for others and themselves.
Well we know how the macro failure, also know a modified method. As a macro developer, should strive to users not in maximum cases error, so we thought directly by a pair of braces to macro content up, probably Everything will be fine. like this?: //Another wrong version of NSLog
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:**FILE**] lastPathComponent] UTF8String], \
**LINE**, **func**); \
(NSLog)((format), ##**VA_ARGS**); \
fprintf(stderr, "-------\n"); \
}
Expand the formula just now, the result is: //I am sorry if you don't like { in the same like. But I am a fan of this style :P if (errorHappend) { fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func); (NSLog)((format), ##VA_ARGS); fprintf(stderr, "-------\n"); }; Compile, Execution, Correct. Because the identification code block with braces is never too much, So in this way we macro in both if got braces circumstances can work! So it seems, In the previous examples of do while really is superfluous? So we can happily issue? If you are careful enough, May have found the problem, It is this last a semicolon. Although the compile and run the test no what problem, but always slightly some dazzling yes or no? Yes, because when we write the NSLog itself, is as a statement to process, followed by a semicolon, in the macro expansion, the semicolon as much out of nightmare. What, you can't see where is the problem? Have a try on this example: if (errorHappend) NSLog(@"Oops, error happened"); else //Yep, no error, I am happy~ :) No! I am not haapy at all! Because the compiler error! In fact, the macro expansion will become like this: if (errorHappend) { fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func); (NSLog)((format), ##VA_ARGS); fprintf(stderr, "-------\n"); }; else { //Yep, no error, I am happy~ :) } Because the else front of more than a semicolon, cause compilation errors, very angry.. if you write the code obediently brackets to write without what thing not? But we have the ingenious solutions, that is above the do while. The macro block of code is added to the do, then while (0), without any change in behavior, but can easily eat that tragic semicolon, use the do version of the while deployment is like this if (errorHappend) do { fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func); (NSLog)((format), ##VA_ARGS); fprintf(stderr, "-------\n"); } while (0); else { //Yep, no error, I am really happy~ :) } This method is widely used to eat a block of code in the macro, has almost become a standard. And while (0) benefit is that, at compile time, the compiler will be fundamental to the optimization for you, this part of content removed, finally compile the results is not because the do while and lead to differences in efficiency. After finally understand this strange do while, we can finally go deep into this macro. Macro body content of the first row without what is worth saying fprintf (stderr, " <%s:%d>%s\n",, simple formatted output. Note that we use, the macro is divided into several lines to write, the actual in the final deployment will be merged into the same row, we just use the backslash MIN finally, I hope you can remember. Next we fill in the three token in the output format,
[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func); It uses three predefined macros, and just like COUNTER, predefined macro behavior is specified by the compiler. The absolute path FILE returns the current file, LINE returns the macro expansion in the number of rows in the func file, where the scope is modified macro function name. We worked on the Log output with this these three parameters, can accelerate the interpretation of Log, rapid positioning. A compiler predefined Log and some of their realization mechanism, interested students can go to GCC document PreDefine pages and clang Builtin Macro to view. Here we will three parameters of the formatted output respectively set the last part for the file name (because the absolute path is too long very ugly), line number, and the name of the method.
<AppDelegate.m : 46> -[AppDelegate application:didFinishLaunchingWithOptions:] 2014-01-20 16:44:25.480 TestProject[30466:70b] The array is ( Hello, My, Macro
With the file, the output line and method, and use the cross bar separates (please forgive me, I didn't like maybe I should draw a cow, such as debug?), it might be easy for some.:)
hello cowsay The Log has three suspense, The first is why we put format alone to write, Then the other parameters as variable parameters? If we don't want that format, While the direct written NSLog (...) will not have a problem? No change for us here in this example, But we need to remember is that... Is the variable argument list, It can represent a, two, Or many parameters, But at the same time, it can also represent zero parameters. If we do not specify the format parameter in the statement of this macro, and directly uses the parameter list, so do not write parameters in the use of the NSLog () will also be matched to the macro, compile fail. If you have a Xcode, also have a look at the implementation of the NSLog method in real Cocoa, can see it also receives a format parameter and a parameter list form, so we defined at the macro, it is to the introduction of appropriate parameters, so that users can use this macro correctly according to the original way.
The second point is that the variable parameter we can accept any arbitrary input, so only in a format input, and a variable number of parameters to zero and what happens? Might as well start look, remember ## function before and after splicing, and the variable parameter is empty now ## after: NSLog(@"Hello"); => do { fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:FILE] lastPathComponent] UTF8String], LINE, func); (NSLog)((@"Hello"), ); fprintf(stderr, "-------\n"); } while (0); The middle line (NSLog) (@" Hello",;) seems to be the problem, you will have doubts, how could this way through the compiler?! The original developers already thought about this problem, and made a point of special treatment. There are special rules, between the comma and VA_ARGS Shuangjing, in addition to splice the text, there is a function, that is if the text is empty, then it will be in front of a comma. This property if and only if the establishment of the above said conditions will be in force, it can be said is a special case. With this rule, we can just formula into the correct (NSLog) ((@" Hello");).
The last one is worthy of being discussed is the place where (NSLog) ((format), ##VA_ARGS); the bracket using the. To look to remove brackets removed, written in NSLog (format, ##VA_ARGS); if you can? Here it is without what big question, first, format will not be called many times did not exist the possibility of misuse (because the compiler can check the NSLog input is correct). And you don't have to worry about following formula where NSLog would again be you, although the expansion of the NSLog also meet the macro definition for us, but the macro expansion is very clever, expansion will own infinite circulation situation, would never again be launched.
As you read here a small reward, with three debug output rect, size and point macros, I hope you can use on (well. Think of how many times a number you need to print these structures were tortured to death, let them play egg go! Of course, please come to understand them.)
Two examples of practical applications
Of course, not to say not described above, the actual use of macros. They are relatively simple, but inside the pit a lot, so it is very characteristic, very suitable for used as an entry. But in fact many of our daily common macro and not so many strange questions, most of the time we follow the idea to realize, pay attention to common problems about the introduction of possible again a little bit, a high quality macros can be born. If you can write some meaningful value of the macro, small to you from the code of the users, the whole world from the entire community and reduce carbon emissions, you have made a considerable contribution. We went through several practical examples to have a look, a macro is how to change our life, and write the code used.
Have a look first to the two macro
_XCTPrimitiveAssertTrue(expression, ## format)
({ \ @try { \ BOOL _evaluatedExpression = !!(expression); \ if (!_evaluatedExpression) { \ _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 0, @#expression),format); \ } \ } \ @catch (id exception) { \ _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 1, @#expression, [exception reason]),format); \ }\ }) If you did perennial Apple, but have not seen or simply don't know what a XCTAssertTrue is, strongly recommend brushing up the testing of knowledge driven development, I think it will be very helpful to you after the road. If you are already familiar with this command, together we started to have a look what is happening behind the scenes.
With the above basis, believe you in general should be able to interpret this macro. ({...})Grammar and ## are familiar with, there are three noteworthy places, in this macro first, behind us is the parameter format..., a style which is also variable parameters, and... And VA_ARGS paired like, {NAME}... Will be used in {NAME}. That is to say, Here the macro content format refers to actually is the definition of the expression took two anti? I am not professional, But I can still remember this said at the university program class, Taking the two anti operation can ensure the result is BOOL, This is quite important in objc (there has been a lot of discussion about objc BOOL, If you are not able to distinguish between BOOL, bool and Boolean, Can this article refer to NSHisper). Then the formula is @#expression. We contacted the Shuangjing ##, and here we see the operator is the single well, #, note well number in front of the @ is the compilation of the objc symbol, the object does not belong to the macro operation. A single well is the role of the string, is simply replaced after two plus "," to a C string. Here the use of @ followed by #expression, it is a content is the content of the expression NSString. Then the NSString is passed as a parameter to the _XCTRegisterFailure and _XCTFailureDescription, continue to expand, which is another story. A simple glance, maybe we can imagine macro help us save a number of things, if you tell me if the write assertions come 10 lines, the imagination will go crazy.
Another example, for people love to see and hear ReactiveCocoa (RAC) in a macro definition. For the RAC not familiar with or have not heard friends, may simply have a look of a series of related posts Limboy (ReactiveCocoa search), the great. If you think "Wow this is good I want to learn", may wish to follow raywenderlich this series of tutorials to do some practice, which simply use the RAC, but it has been included in the basic use of RAC. There are several important macro RAC, which is the guarantee of the basic RAC simple and easy to use, can be said that without this several macro words, there is no one like RAC. Where RACObserve is one of them, a property which through the KVC object to create a signal return (if you cannot read this sentence, don't worry, this to your understanding of the macro way and no effect). For this macro, I decided not to like the above expansion and explanation, I would at last the macros are sticking out, we might as well take it practice, have a look can expand it to the code of the state, and understand what happened. If you encounter what problem or experience during deployment in the comments, welcome message to share and exchange:)
Well, this article has been long enough. In the hope that after you see in macro will no longer afraid, but can be very happy to say that I will I will I will. The ultimate goal of course is to write pretty simple and efficient macro, this to improve productivity or deter your colleagues to enhance their own strength will be very helpful.
In addition, here to propagandize the concern for a long time @hangcom Wu Hang senior book "iOS application of reverse engineering . Very honored to receive advanced allowed to read the whole book before release, can be said to see the fun sweating. I didn't break any base development, much less related knowledge, follow the book tutorial and examples in the premise of exploration process can be said to be very interesting. I have been able to use different vision and height to look at this years engaged in the development of iOS industry, benefit. You can say "iOS application of reverse engineering" is my recent pleasant reading to cool is a good book. Now this book is still in advance, but from January 28th officially on sale is very close, the relevant page interested students can go to Amazon or ChinaPub book, this book will be iOS technology personnel great festival books.
Finally, we agreed we play is to practice, I added some notes to help you a little understanding of each macro is doing what, in the back left a piece of experimental field, you can fill in the play. In short, refueling! //The call to RACSignal is the name of the class RACSignal *signal = [RACObserve(self, currentLocation)];
//The following is the beginning of a macro definition //Rac_valuesForKeyPath:observer: is the method name
[(id)(TARGET) rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]
metamacro_if_eq(1, metamacro_argcount(**VA_ARGS**))(keypath1(**VA_ARGS**))(keypath2(**VA_ARGS**))
//This macro in keypath also made at compile time to determine whether keypath exists, to avoid the mistake of writing //You may not mind the witchcraft
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
//A and B are equal, if the same is the first for the back, otherwise the expansion to second behind //eg. metamacro_if_eq(0, 0)(true)(false) => true // metamacro_if_eq(0, 1)(true)(false) => false
metamacro_concat(metamacro_if_eq, A)(B)
metamacro_concat(metamacro_if_eq0_, VALUE)
metamacro_at(20, **VA_ARGS**, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
metamacro_concat(metamacro_at, N)(**VA_ARGS**)
metamacro_concat_(A, B)
metamacro_head_(**VA_ARGS**, 0)
metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
http://www.cnblogs.com/muzinian/archive/2012/11/25/2787929.html
宏定义可以包含两个专用的运算符:#和##。编译器不会识别这两个运算符,他们会预处理时被执行。
#define PRINT_INT(n) printf(#n " = %d\n",n)
n之前的#运算符通知预处理器根据PRINT_INT的参数创建一个字符串字面量。因此,调用
PRINT_INT(i/j);
会变为
printf("i/j" " = %d\n",i/j);
等价为printf("i/j = %d\n",i/j);
#define MK_ID(n) i##n
当MK_ID被调用时(MK_ID(1)),预处理器首先使用实际参数替换形式参数n。接着,预处理器将i和1合并成为一个记号(i1)。下面的声明使用MK_ID创建了3个标识符:
int MK_ID(1),MK_ID(2),MK_ID(3);
预处理后这一声明变为:int i1,i2,i3;
如果要被“字符串化”的参数包含“或\字符,#运算符会将"转换为\",\转换为\。考虑下面的宏:
预处理器会将STRINGIZE("foo")替换为”\“foo\""。
替换列表中依赖##的宏通常不能嵌套调用。如:
有一种办法可以解决这个问题,但不好看。技巧是再定义一个宏来调用第一个宏:
用CONCAT2(a,CONCAT2(b,c))就会得到abc。
以上来源于C语言程序设计_现代方法(第2版)
但是,如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。例如,可以将它与字符串连接运算结合起来编写一个调试打印宏:#define dprint(expr) printf(#expr " = %g\n", expr)使用语句dprint(x/y)调用该宏时,该宏将被扩展为:printf("x/y" " = &g\n", x/y);其中的字符串被连接起来了,这样,该宏调用的效果等价于printf("x/y = &g\n", x/y);在实际参数中,每个双引号"将被替换为\",反斜杠\将被替换为\,因此替换后的字符串是合法的字符串常量。预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。例如,下面定义的宏paste用于连接两个参数#define paste(front, back) front ## back因此,宏调用paste(name, 1)的结果将建立记号name1。
两个特殊的运算符会影响替换过程。首先,如果替换记号序列中的某个形式参数前面直接是一个#符号(它们之间没有空白符),相应形式参数的两边将被加上双引号("),随后,#和形式参数标识符将被用引号引起来的实际参数替换。实际参数中的字符串字面值、字符常量两边或内部的每个双引号(")或反斜杠(\)前面都要插入一个反斜杠(\)。其次,无论哪种宏的定义记号序列中包含一个##运算符,在形式参数替换后都要把##及其前后的空白符都删除掉,以便将相邻记号连接起来形成一个新记号。如果这样产生的记号无效,或者结果依赖于##运算符的处理顺序,则结果没有定义。同时,##也可以不出现在替换记号序列的开头或结尾。对这两种类型的宏,都要重复扫描替换记号序列以查找更多的已定义标识符。但是。当某个标识符在某个扩展中被替换后,再次扫描并再次遇到此标识符时不再对其执行替换,而是保持不变。即使执行宏扩展后得到的最终结果以#打头,也不认为它是预处理指令。说明:有关宏扩展处理的细节信息,ANSI标准比第1版描述得更详细。最重要的变化是加入了#和##运算符,这就使得引用和连接成为可能。某些新规则(特别是与连接有关的规则)比较独特(参见下面的例子)。
例如,这种功能可用来定义“表示常量”,如下例所示:
int table[TABSIZE];
定义
定义了一个宏,它返回两个参数之差的绝对值。与执行同样功能的函数所不同的是,参数与返回值可以是任意算术类型,甚至可以是指针。同时,参数可能有副作用,而且需要计算两次,一次进行测试,另一次则生成值。假定有下列定义:
宏调用tempfile(/usr/tmp)将生成"/usr/tmp" "%s"随后,该结果将被连接为一个单个的字符串。给定下列定义:#define cat(x, y) x ## y那么,宏调用cat(var, 123)将生成var123。但是,宏调用cat(cat(1,2),3)没有定义:##阻止了外层调用的参数的扩展。因此,它将生成下列记号串:cat ( 1 , 2 )3并且,)3 (不是一个合法的记号,它由第一个参数的最后一个记号与第二个参数的第一个记号连接而成。如果再引入第二层的宏定义,如下所示:#define xcat(x, y) cat(x,y)我们就可以得到正确的结果。xcat(xcat(1, 2), 3)将生成123,因为xcat 自身的扩展不包含##运算符。类似地,ABSDIFF(ABSDIFF(a,b),c)将生成所期望的经完全扩展后的结果。
以上来源C程序设计语言(第2版)
cat(cat(1,2), 3) 展开就成 cat(1,2)3 这个表达式无法继续使用
xcat(xcat(1,2),3) 展开 cat(xcat(1,2),3) 再展开cat(12,3)
如果cat(cat(cat(1,2),3),4),展开了就变成了cat(cat(1,2),3)4
xcat(xcat(1,2),3) 展开 xcat(cat(1,2),3) 再展开xcat(12,3)
xcat(ca,t)(1,2) 展开就是cat(1,2) 然后继续展开12
xcat(ca,t)(1,2) -> ca ## t(1,2)
ca ## t(1,2) -> cat(1,2) 以上,到第一个右括号,就截止了 然后就开始展开xcat这个宏了
以下宏定义代码没有看懂,应该是调用的时候,直接使用
等价于调用
但是,后面有多个反斜杠,说明后面的定义是一行,猜测是根据情况,来调用不同的宏定义,但是还是没有完全懂。多个反斜杠又是为什么呢?多个反斜杠合法吗? XX相关代码如下。