寫這篇原創文章是因為看到了極客中的一篇文章《有趣各種程式設計語言實作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啥的光死看書不中啊,要學以緻用啊,在此抛磚引玉,謝謝各位觀賞哦。
