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

[email protected]: 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

[email protected]: cs$clang -std=c99 -Wall -O3 -g0 -o 5 5.c

[email protected]: cs$./5

0x7fff57925936 0x7fff57925934 0x7fff57925933

2 + 2 = 5

所以说学C啥的光死看书不中啊,要学以致用啊,在此抛砖引玉,谢谢各位观赏哦。

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

时间: 2024-10-05 04:09:42

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

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

原文链接:C语言里为何会有“2+2=5”的结果 写这篇原创文章是因为看到了极客中的一篇文章<有趣各种编程语言实现2+2=5>,其中C语言是这样实现的: 1 int main() { 2 char __func_version__[] = “5″; // For source control 3 char b[]=”2″, a=2; 4 printf(“%d + %s = %s\n”, a, b, a+b); 5 return 0; 6 } 有些童鞋可能会说,这不是偷换概念吗,拿字符串和int相

C语言里全局变量管理

C语言里信息封装比較弱,仅仅有静态变量的文件作用域. 假设不加约束.非常easy造成全局变量满天飞. 假设定义一个全局结构体.把全局变量都放到这个GlobleVariate里,应该好管一些,至少比裸奔文雅一点. 更进一步,每一个模块定义自己的结构体.把模块公共变量放到结构体里.这样把GlobleVariate拆成多个小结构体,会更文雅一些. 大概的伪代码是以下这个样子. struct GlobleVariate { struct ModuleVariate1: struct ModuleVari

c语言里NULL的理解

NULL一看名字就知道为空,什么为空呢,而且全是大写.是不是意味着是个宏定义呢?如果想到这里,我相信你离真理不远了. 有些人为什么犯错?因为只看到了NULL这四个字母而已,没有看到事物的本质,老师在课堂上也只是说空指针,空字符串..这样只会陷入无止境的误区.如果我这样定义: #define NULL 0 你是不是又该纠结了呢? 正确的做法是把它当作一个宏,不管如何变化,来展开看看就知道了. 在C语言的头文件stddef.h中,NULL的定义如下: #define NULL #define __c

const分别在C和C++语言里的含义和实现机制

const的含义        简单地说:const在c语言中表示只读的变量,而在c++语言中表示常量. C语言 const是constant的缩写,是恒定不变的意思,也翻译为常量,但是很多人都认为被const修饰的值都是常量,其实这是不精确的.因为,精确来说应该是只读的变量,其值在编译的时候不能被使用,因为编译器在编译的时候不知道其存储的内容.或许当初这个关键字应该被替换为readonly. C语言中const定义的变量只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,

嵌入式C语言里的土豪们之除法与移位

土豪这个词用在接下来要出场的C语言再合适不过了.他们在嵌入式C语言里占尽了奢华,但是毕竟我们更需要一个节约型的微生态环境.在这里简单给大家分析一下嵌入式C语言编程时用到的除法与移位. 本文引用地址:http://www.eepw.com.cn/article/182359.htm 除法土豪 除法在嵌入式微处理器里可算是一个消耗大户,复杂的实现方式不仅占用了大师宝贵的计算时间而且精度有限情况下占用了大片的RAM.因此,常常在各类文章里看到,编程人员应该使用右移运算来代替除法.这个右移方法没有问题,

理解Python语言里的异常(Exception)

Exception is as a sort of structured "super go to".异常是一种结构化的"超级goto". 作为一个数十年如一日地钟爱C语言的程序员(因为C程序员需要记忆的关键字很少,而且可以很惬意地玩内存),对于高级语言如Python里的异常(Exception)一直不甚理解,尤其是其实现机理.但读了<Learning Python>一书中上面这句话(尤其是goto关键字)后,忽然豁然开朗. 如果用C语言编写一个鲁棒性良

C语言里字符串的解析

原文网摘:http://www.cnblogs.com/yi-meng/p/3620244.html#undefined 根据给定的字符串,按照一定规则解析字符串,卡住好几次,这次做个笔记,以供参考 函数名称:   strtok 函数原型:   char *strtok(char *s1, const char *s2) 函数功能:   分解s1字符串为用特定分隔符分隔的多个字符串(一般用于将英文句分解为单词) 函数返回:   字符串s1中首次出现s2中的字符前的子字符串指针 参数说明:   s

C语言里的左移和右移运算

先说左移,左移就是把一个数的所有位都向左移动若干位,在C中用<<运算符.例如: int i = 1;i = i << 2;  //把i里的值左移2位 也就是说,1的2进制是000...0001(这里1前面0的个数和int的位数有关,32位机器,gcc里有31个0),左移2位之后变成000...0100,也就是10进制的4,所以说左移1位相当于乘以2,那么左移n位就是乘以2的n次方了(有符号数不完全适用,因为左移有可能导致符号变化,下面解释原因) 需要注意的一个问题是int类型最左端

[R]R语言里的异常处理与错误控制

之前一直只是在写小程序脚本工具,几乎不会对异常和错误进行控制和处理. 随着脚本结构和逻辑更复杂,脚本输出结果的准确性验证困难,同时已发布脚本的维护也变得困难.所以也开始考虑引入异常处理和测试工具的事情. 不过好像R语言的异常处理似乎有些辣鸡?查了下资料和try的文档说明,感觉说的并不清楚. 在网上查了一些资料,对R语言异常处理做了比较详细的说明,留档作为参考.至于测试工具的问题,后续还是再考虑下. 文章链接:R语言-处理异常值或报错的三个示例 原文参考了以下几个网页: http://stacko