一切都要从NAN这个看起来平平无奇的宏说起。前几天lz正在捣鼓C++的numeric_limits,它有一个成员函数quiet_NaN,GCC和Clang的实现是这样的:


我依稀记得宏NAN好像也是这么实现的,为了验证这个已然模糊不清的印象,lz迫不及待地打开了<float.h>。

果然是这样,看来我的记性还不算差。
其实lz曾经不止一次地翻看过<float.h>,不过当时大抵是走马观花罢了,然而这一次,我的目光被这个熟悉而又陌生的宏定义吸引住了。
再仔细看一眼?

欸?不对呀,这怎么是个函数调用啊?C标准明明要求NAN展开成一个常量表达式,函数调用怎么可能是常量表达式,C语言有返回常量表达式的函数吗?
难不成是GCC对<float.h>施了什么魔法?可是从头认真看了一遍<float.h>之后,也没发现这个标准头有任何特殊的地方。
既然如此,就只好亲自编写代码为__builtin_nanf验明正身了,是真常量还是伪君子,一试便知!

编译,运行!

竟然通过了?!看来确实不关<float.h>的事,__builtin_nanf("")就是可以在任何位置表现出常量表达式的性质。
等等,先不要急着下结论,说不定GCC在编译过程中做了什么手脚,先看看预处理器怎么说,gcc -E走起。

__builtin_nanf纹丝不动,说明它并非是一个用来混淆视线的宏,现在可以肯定GCC不是在预处理阶段,而是在编译阶段把__builtin_nanf("")给换掉了,再看看IDA怎么说。

__builtin_nanf不见了,GCC把几个寄存器倒来倒去,其实就是把float类型的NAN转换成了double类型的NAN,果然是这样。
折腾了一通,最后发现__builtin_nanf("")被编译器替换成了一个立即数,这显然不是我们想要的,既然GCC手册上说__builtin_nanf是一个函数,那么我们一定要让这个函数显露出它的庐山真面目。
首先,函数是可以取地址的;其次,能接受字符串字面量参数的函数,也必定能接受字符数组名。思路已经有了,测试代码立刻安排上。

编译,运行,printf不出意料地输出了一个正常的地址,__builtin_nanf也笑纳了我们输入的字符串,并且依旧返回了一个NAN,最终程序也正常退出了。

这里需要说明一点,如果参数是字符数组名,__builtin_nanf的返回值就不是常量表达式了。