程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> C語言裡為何會有“2+2=5”的結果

C語言裡為何會有“2+2=5”的結果

編輯:關於C

寫這篇原創文章是因為看到了極客中的一篇文章《有趣各種編程語言實現2+2=5》,其中C語言是這樣實現的:

int main() {
char __func_version__[] = “5″; // For source control
char b[]=”2″, a=2;
printf(“%d + %s = %s\n”, a, b, a+b);
return 0;
}

有些童鞋可能會說,這不是偷換概念嗎,拿字符串和int相加,是滴,但在這裡請這些童鞋暫且幽默一回,想一想為何a+b會得出5的結果?你們實際動手編譯了嗎?結果是為5嗎?

我動手編譯了,結果不是5,確切的說是一個不可打印的ascii字符,所以console顯示的是:2+2= ,稍對C堆棧布局略有了解的都知道,其實這段代碼最後試圖打印的是__func_version__裡的字符串"5",但遺憾的是不同編譯器,甚至同一種編譯器用不通編譯選項生成得stack布局是截然不同的,這就無法保證精確定位b之後3字節正好指向__func_version__。

那麼在gcc -O3下到底布局如何呢?我們略微修改一下代碼:

#include 

int main() 
{
	//char b[]="2", a=2;
	char __func_version__[] = "5"; // For source control

	char b[]="2", a=2;
	printf("%p %p %p\n",__func_version__,b,&a);
/*
	for(int i=0;i<100;++i){
		printf("%d + %s = %s\n", i, b, i+b);
	}
*/
	printf("%d + %s = %s\n", a, b, a+b);
	return 0;
}

我們來看一下結果:

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很簡單的我們可以添加如下代碼:

#pragma pack(1)

typedef struct __foo {
	char *b;
	char a;
	char *__func_version__;
}foo;

void print_5_by_struct(void)
{
	foo foo_v = {"2",(char)2,"5"};
	printf("%p %p\n",foo_v.__func_version__,foo_v.b);
	printf("%d + %s = %s\n",foo_v.a,foo_v.b,foo_v.a+foo_v.b);
}

最終如願以償的打印了“2+2=5”,如果有其他童鞋知道gcc如何pack變量布局的,請告知本貓,在此感謝。

有些童鞋又會說了,你這樣結構太累贅鳥,太墨跡,不爽快!也好辦,沒說只能用gcc啊,我們試試clang吧 :)

#include 

int main() 
{
	char __func_version__[] = "5"; // For source control
	char b[]="2", a=2;

	printf("%p %p %p\n",__func_version__,b,&a);
	printf("%d + %s = %s\n", a, b, a+b);
	return 0;
}

shell編譯運行如下:

clang -v

Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)

Target: x86_64-apple-darwin13.2.0

Thread model: posix

apple@kissAir: cs$clang -std=c99 -Wall -O3 -g0 -o 5 5.c

apple@kissAir: cs$./5

0x7fff57925936 0x7fff57925934 0x7fff57925933

2 + 2 = 5


所以說學C啥的光死看書不中啊,要學以致用啊,在此拋磚引玉,謝謝各位觀賞哦。大笑

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved