C语言里为何会有“2+2=5”的结果

    写这篇原创文章是因为看到了极客中的一篇文章《有趣各种编程语言实现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 <stdio.h>

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 <stdio.h>

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啥的光死看书不中啊,要学以致用啊,在此抛砖引玉,谢谢各位观赏哦。

时间: 2024-12-11 11:34:23

C语言里为何会有“2+2=5”的结果的相关文章

Win8系统中文语言里添加英文输入法失败怎么解决?

  Win8系统中文语言里添加英文输入法失败怎么解决?           1.首先,在桌面上单击鼠标右键,选择"新建"-->"文本文档";复制下面代码到其中; 2.将此txt文件另存为可操作注册表的reg文件,依次选择记事本菜单栏的"文件"-->"另存为",确定好保存位置,然后在选择最下面的"保存类型"为"所有文件",在上面"文件名"一项填入:中文语言下

Go语言里的new函数用法分析_Golang

本文实例讲述了Go语言里的new函数用法.分享给大家供大家参考.具体如下: 表达式 new(T) 分配了一个零初始化的 T 值,并返回指向它的指针. var t *T = new(T) 或 t := new(T) 代码如下: 复制代码 代码如下: package main import "fmt" type Vertex struct {     X, Y int } func main() {     v := new(Vertex)     fmt.Println(v)     v

Go语言里的结构体文法实例分析_Golang

本文实例讲述了Go语言里的结构体文法.分享给大家供大家参考.具体分析如下: 结构体文法表示通过结构体字段的值作为列表来新分配一个结构体. 使用 Name: 语法可以仅列出部分字段.(字段名的顺序无关.) 特殊的前缀 & 构造了指向结构体文法的指针. 复制代码 代码如下: package main import "fmt" type Vertex struct {     X, Y int } var (     p = Vertex{1, 2}  // has type Ver

go语言里包的用法实例_Golang

本文实例讲述了go语言里包的用法.分享给大家供大家参考.具体分析如下: 每个 Go 程序都是由包组成的. 程序运行的入口是包 main. 这个程序使用并导入了包 "fmt" 和 "math". 按照惯例,包名与导入路径的最后一个目录一致. 复制代码 代码如下: package main import (  "fmt"  "math" ) func main() {  fmt.Println("Happy",

c语言-C语言里用一个空格代替连续出现的多个空格,然后输出,求解!

问题描述 C语言里用一个空格代替连续出现的多个空格,然后输出,求解! #include int main(){ int c i j; char str[1000]; i = 0;while ((c = getchar()) != EOF) { str[i] = c; ++i;}str[i] = '';i = j = 0;while (str[i] != '') { putchar(str[i]); ++i; j = i; while (str[i] == ' ') ++i; if (i - j

c小知识点-C语言里面的EOF与 傻傻分不清楚

问题描述 C语言里面的EOF与n傻傻分不清楚 eof和n如何区别 作为初学者,不太懂. 谢谢 解决方案 n是换行(0),eof是结束(-1) 比如 char c = getchar() ; if (c == '') 换行 以及 if (scanf("%c", &c) == EOF) 输入结束 解决方案二: n 回车, 是一个字符 eof 文件结束符 -1 解决方案三: #define EOF -1 回车->'rn' 解决方案四: eof End of file n是回车符

c语言-大家帮我看一下,这个在C语言里调用shell的对嘛,谢谢

问题描述 大家帮我看一下,这个在C语言里调用shell的对嘛,谢谢 #include #include int main(int argc,char**argv){ FILE* fp=NULL; fp=fopen("passwd.txt","r"); char buff[20]; int i; for(i=0;;i++){ memset(buff,0,sizeof(buff)); if(fp==NULL) break; fgets(buff,20,fp); prin

c语言里面的sbrk()函数与malloc()函数的区别

问题描述 c语言里面的sbrk()函数与malloc()函数的区别 c语言里面的sbrk()函数与malloc()函数的区别有哪些?最近刚看<unix环境高级编程>里面有,不懂,求大神详解,小弟多谢

函数调用-C++ string类里面有类似C语言里sscanf和sprintf的函数吗?

问题描述 C++ string类里面有类似C语言里sscanf和sprintf的函数吗? C++ string类里面有类似C语言里sscanf和sprintf的函数可以用吗?或者怎么简易实现从string类读入或输出字符.字符串.整型数等等.最好是库函数,有详细举例,不要太复杂. 解决方案 # include<iostream> using namespace std; int main(void) { int a; cout<<"请输入一个数字,按回车结束"&

c语言-C语言里想要用函数创建一个新的字符数组,并使其等于原有的一个字符数组该怎么做?

问题描述 C语言里想要用函数创建一个新的字符数组,并使其等于原有的一个字符数组该怎么做? #include #include #include char map[4][4]; char creat()//创建一个新的字符数组 { char *maze=(char)malloc(sizeof(map)); return maze; } void main() { int i,j; for(i=0;i<4;i++) { gets(map[i]); } char *maze=creat(); strc