写这篇原创文章是因为看到了极客中的一篇文章《有趣各种编程语言实现2+2=5》,其中c语言是这样实现的:
有些童鞋可能会说,这不是偷换概念吗,拿字符串和int相加,是滴,但在这里请这些童鞋暂且幽默一回,想一想为何a+b会得出5的结果?你们实际动手编译了吗?结果是为5吗?
我动手编译了,结果不是5,确切的说是一个不可打印的ascii字符,所以console显示的是:2+2= ,稍对c堆栈布局略有了解的都知道,其实这段代码最后试图打印的是__func_version__里的字符串"5",但遗憾的是不同编译器,甚至同一种编译器用不通编译选项生成得stack布局是截然不同的,这就无法保证精确定位b之后3字节正好指向__func_version__。
那么在gcc -o3下到底布局如何呢?我们略微修改一下代码:
我们来看一下结果:
gcc -v
using built-in specs.
collect_gcc=gcc
collect_lto_wrapper=/usr/local/cellar/gcc48/4.8.2/libexec/gcc/x86_64-apple-darwin13.0.0/4.8.2/lto-wrapper
target: x86_64-apple-darwin13.0.0
configured with: ../configure --build=x86_64-apple-darwin13.0.0 --prefix=/usr/local/cellar/gcc48/4.8.2 --enable-languages=c,c++,objc,obj-c++ --program-suffix=-4.8 --with-gmp=/usr/local/opt/gmp4 --with-mpfr=/usr/local/opt/mpfr2 --with-mpc=/usr/local/opt/libmpc08
--with-cloog=/usr/local/opt/cloog018 --with-isl=/usr/local/opt/isl011 --with-system-zlib --enable-version-specific-runtime-libs --enable-libstdcxx-time=yes --enable-stage1-checking --enable-checking=release --enable-lto --disable-werror --enable-plugin --disable-nls
--disable-multilib
thread model: posix
gcc version 4.8.2 (gcc)
cs$gcc -std=c99 -wall -o3 -g0 -o 5 5.c
apple@kissair: cs$./5
0x7fff504fa920 0x7fff504fa930 0x7fff504fa910
2 + 2 = op?
纳尼!肿么__func_version__还比b要小,那么不管b加什么正数都无法指向前者了,当然有些人会说了,可以整数回绕啊,我呵呵了。那也不行哦,那样就不是“2+2=5”鸟,而是"2+xxxxxxxxxx=5"鸟了哦。虽然可以改变两个字符数组变量的位置来解决这一问题,即b[]定义放在__func_version__前面,但那也要"2+16=5"哦,我不知道gcc有没有什么编译选项可以pack堆栈变量滴,但我知道#pragma pack(1)是可以打包结构变量滴,so很简单的我们可以添加如下代码:
最终如愿以偿的打印了“2+2=5”,如果有其他童鞋知道gcc如何pack变量布局的,请告知本猫,在此感谢。
有些童鞋又会说了,你这样结构太累赘鸟,太墨迹,不爽快!也好办,没说只能用gcc啊,我们试试clang吧 :)
shell编译运行如下:
clang -v
apple llvm version 5.1 (clang-503.0.40) (based on llvm 3.4svn)
target: x86_64-apple-darwin13.2.0
apple@kissair: cs$clang -std=c99 -wall -o3 -g0 -o 5 5.c
0x7fff57925936 0x7fff57925934 0x7fff57925933
2 + 2 = 5
所以说学c啥的光死看书不中啊,要学以致用啊,在此抛砖引玉,谢谢各位观赏哦。
