linux中命令cpp是C Preprocessor的缩写,而不是C++那个后缀名称。
先来个关于printf奇怪用法的程序,这个程序是一个牛人写的,得过大奖,背景在此就不多说了,有闲情想了解的同志们去百度吧。在linux或unix平台上运行的结果是unix。
1 | main(){printf(&unix["\020%six\012\0"],(unix)["have"]+"fun"-0x60); } |
这个程序在CU上有帖子讨论:http://bbs.chinaunix.net/thread-580042-1-1.html。
在Linux forum上也有讨论:http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=program&Number=386653&page=2&view=collapsed&sb=5&o=all&fpart=。
下面是后者中的一些解答部分,本人才疏学浅,不敢在此发表自己见解。
因为:数组str[n] <==>(n)[str]
所以:(unix)["have"] + "fun" - 0x60 <==> "have"[1]+"fun"-0x60
即: 'a'+"fun"-0x60
因为:'a' = 0x61
所以:变为“fun”+1(隐含了同类型运算优先?)
因为:“fun”是个char型指针
所以:指向“un”
因为:\021是一个八进制数,代表第一个字符
所以:&1["\021unix\012\0"]<==>"unix\012\0"
因为:012是八进制的换行,\0是字符串结束
==所以:最终结果是unix==
代码示例过程:
1 | printf(&unix["/021%six/012/0"], (unix)["have"] + "fun" - 0x60); |
理解这个程序需要知道数组的那个诡异但正确的用法,取地址,指针运算,UNIX/Linux编译器的熟悉程度(说白了就是要知道unix是一个已经预定义好的宏,见下文),当然还要知道十六进制和八进制表示方法。
1 | $cpp -dM /dev/null > macro-on-linux.txt |
(其中的/dev/null可用C文件代替,添加选项-std=c99可得不同结果,详细用法请man cpp,实际结果请实践之)
macro-on-linux.txt文件中内容如下,这里删除了其中很多内容,其中unix藏在某个角落里,所以,上面的程序如果使用下面许多宏来代替,结果是一样的。
1 | #define __DBL_MIN_EXP__ (-1021) |
这些宏都是预定义好了的,就像__cpluscplus
,__func__
那些一样。网上说是编译器内建的,在具体文件是找不到的,我功底不深,不能从原理上解释,见谅。
个人觉得,如果想在linux下搞些开发,这些知识应该要懂一点,多知道一些总不是坏事。