原文链接:C语言里为何会有“2+2=5”的结果

写这篇原创文章是因为看到了极客中的一篇文章《有趣各种编程语言实现2+2=5》,其中C语言是这样实现的:

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

有些童鞋可能会说,这不是偷换概念吗,拿字符串和int相加,是滴,但在这里请这些童鞋暂且幽默一回,想一想为何a+b会得出5的结果?你们实际动手编译了吗?结果是为5吗?

我动手编译了,结果不是5,确切的说是一个不可打印的ascii字符,所以console显示的是:2+2= ,稍对C堆栈布局略有了解的都知道,其实这段代码最后试图打印的是__func_version__里的字符串"5",但遗憾的是不同编译器,甚至同一种编译器用不通编译选项生成得stack布局是截然不同的,这就无法保证精确定位b之后3字节正好指向__func_version__。

那么在gcc -O3下到底布局如何呢?我们略微修改一下代码:

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5. //char b[]="2", a=2;
  6. char __func_version__[] = ""; // For source control
  7.  
  8. char b[]="", a=;
  9. printf("%p %p %p\n",__func_version__,b,&a);
  10. /*
  11. for(int i=0;i<100;++i){
  12. printf("%d + %s = %s\n", i, b, i+b);
  13. }
  14. */
  15. printf("%d + %s = %s\n", a, b, a+b);
  16. return ;
  17. }

我们来看一下结果:

  1. gcc -v
  2.  
  3. Using built-in specs.
  4.  
  5. COLLECT_GCC=gcc
  6.  
  7. COLLECT_LTO_WRAPPER=/usr/local/Cellar/gcc48/4.8./libexec/gcc/x86_64-apple-darwin13.0.0/4.8./lto-wrapper
  8.  
  9. Target: x86_64-apple-darwin13.0.0
  10.  
  11. Configured with: ../configure --build=x86_64-apple-darwin13.0.0 --prefix=/usr/local/Cellar/gcc48/4.8. --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
  12.  
  13. Thread model: posix
  14.  
  15. gcc version 4.8. (GCC)
  16.  
  17. cs$gcc -std=c99 -Wall -O3 -g0 -o .c
  18.  
  19. apple@kissAir: cs$./
  20.  
  21. 0x7fff504fa920 0x7fff504fa930 0x7fff504fa910
  22.  
  23. + = OP?

纳尼!肿么__func_version__还比b要小,那么不管b加什么正数都无法指向前者了,当然有些人会说了,可以整数回绕啊,我呵呵了。那也不行哦,那样就不是“2+2=5”鸟,而是"2+xxxxxxxxxx=5"鸟了哦。虽然可以改变两个字符数组变量的位置来解决这一问题,即b[]定义放在__func_version__前面,但那也要"2+16=5"哦,我不知道gcc有没有什么编译选项可以pack堆栈变量滴,但我知道#pragma pack(1)是可以打包结构变量滴,so很简单的我们可以添加如下代码:

  1. #pragma pack(1)
  2.  
  3. typedef struct __foo {
  4. char *b;
  5. char a;
  6. char *__func_version__;
  7. }foo;
  8.  
  9. void print_5_by_struct(void)
  10. {
  11. foo foo_v = {"",(char),""};
  12. printf("%p %p\n",foo_v.__func_version__,foo_v.b);
  13. printf("%d + %s = %s\n",foo_v.a,foo_v.b,foo_v.a+foo_v.b);
  14. }

最终如愿以偿的打印了“2+2=5”,如果有其他童鞋知道gcc如何pack变量布局的,请告知本猫,在此感谢。

有些童鞋又会说了,你这样结构太累赘鸟,太墨迹,不爽快!也好办,没说只能用gcc啊,我们试试clang吧 :)

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5. char __func_version__[] = ""; // For source control
  6. char b[]="", a=;
  7.  
  8. printf("%p %p %p\n",__func_version__,b,&a);
  9. printf("%d + %s = %s\n", a, b, a+b);
  10. return ;
  11. }

shell编译运行如下:

  1. clang -v
  2.  
  3. Apple LLVM version 5.1 (clang-503.0.) (based on LLVM .4svn)
  4.  
  5. Target: x86_64-apple-darwin13.2.0
  6.  
  7. Thread model: posix
  8.  
  9. apple@kissAir: cs$clang -std=c99 -Wall -O3 -g0 -o .c
  10.  
  11. apple@kissAir: cs$./
  12.  
  13. 0x7fff57925936 0x7fff57925934 0x7fff57925933
  14.  
  15. + =

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

[原创]C语言里为何会有“2+2=5”的结果的更多相关文章

  1. c语言里用结构体和指针函数实现面向对象思想

    一.基础研究 观察如下两个程序a.c和b.c: A.c: B.c: 这两个程序都是要实现在屏幕上第10行40列打印一个绿色的字符c: 这两个程序的数据组织方式是一样的,都是使用结构体,而且对共性和个性 ...

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

    const的含义        简单地说:const在c语言中表示只读的变量,而在c++语言中表示常量. C语言 const是constant的缩写,是恒定不变的意思,也翻译为常量,但是很多人都认为被 ...

  3. c语言里如何调用汇编里的变量?

    c语言里如何调用汇编里的变量? 汇编语言:是声明全局变量 .globl _end_ofs _end_ofs: .word _end - _start c语言:声明这个变量,然后再调用这个变量 void ...

  4. C语言里全局变量管理

    C语言里信息封装比較弱,仅仅有静态变量的文件作用域. 假设不加约束.非常easy造成全局变量满天飞. 假设定义一个全局结构体.把全局变量都放到这个GlobleVariate里,应该好管一些,至少比裸奔 ...

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

    写这篇原创文章是由于看到了极客中的一篇文章<有趣各种编程语言实现2+2=5>,当中C语言是这样实现的: int main() { char __func_version__[] = &qu ...

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

    之前一直只是在写小程序脚本工具,几乎不会对异常和错误进行控制和处理. 随着脚本结构和逻辑更复杂,脚本输出结果的准确性验证困难,同时已发布脚本的维护也变得困难.所以也开始考虑引入异常处理和测试工具的事情 ...

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

    Exception is as a sort of structured "super go to".异常是一种结构化的"超级goto". 作为一个数十年如一日 ...

  8. 怎样在C语言里实现“面向对象编程”

    有人觉得面向对象是C++/Java这样的高级语言的专利,实际不是这样.面向对象作为一种设计方法.是不限制语言的.仅仅能说,用C++/Java这样的语法来实现面向对象会更easy.更自然一些. 在本节中 ...

  9. C语言里字符串的解析

      根据给定的字符串,按照一定规则解析字符串,卡住好几次,这次做个笔记,以供参考 函数名称:   strtok 函数原型:   char *strtok(char *s1, const char *s ...

随机推荐

  1. Stat

    Description 请你编程实现一个简单(渣渣)的文本编辑器,具体要求是:给定一个单词,请你输出它在给定的文章中出现的次数和第一次出现的位置.注意:匹配单词时,不区分大小写,但要求完全匹配,即给定 ...

  2. git撤销修改

    在Git中,删除也是一个修改操作,我们实战一下,先添加一个新文件test.txt到Git并且提交: $ git add test.txt $ git commit -m "add test. ...

  3. Django后台管理界面

    之前的几篇记录了模板视图.模型等页面展示的相关内容,这篇主要写一下后台admin管理界面的内容. 激活管理界面 Django管理站点完全是可选择的,之前我们是把这些功能给屏蔽掉了.记得上篇中Djang ...

  4. c语言 文件写入和读取

    #include<stdio.h> #include<stdlib.h> #include<string.h> #define N 10 struct studen ...

  5. 进程占用百分百CPU不卡(从未试过,当别的程序运行的时候,当前程序还会运行吗?)

    在写程序中.为了让程序效率高.有时会点用很高的CPU.这里用户体验不好可以设置线程的优先级来搞定. BOOL SetThreadPriority( HANDLE hThread, // handle ...

  6. ASP.NET自定义控件加载资源WebResource问题

    最近项目用日期控件,想把My97的资源文件跟TextBox封装成一个DatePicker控件,其实很简单的意见事情,但是还是用了一天多的时间,主要的问题就是解决资源文件加载的问题.通过一天多的努力,得 ...

  7. 实用chrome插件

    2015年最实用的9款chrome插件 随着14年chrome浏览器的市场超过IE浏览器,chrome凭借它强劲性能和出色的使用体验真正的登上了平民级的殿堂.今天小编就为大家推荐9款自己常用的chro ...

  8. 线性表A-B

    1.顺序存储 #include<stdio.h> /* 设有两个顺序表A和B,且都递增有序,试写一算法,从A中删除与B中相同的那些元素,即求A-B */ #define getArrayL ...

  9. 安装virtualbox虚拟机的增强功能

    转自:http://wubangtu.com/714 最近有很多人问我这个问题,现在全部写在这里,免得到时候又啰嗦一遍了,哈哈.欢迎大家前来围观: 安装virtualbox虚拟机的增强功能可以实现如下 ...

  10. Android的logcat命令详解

    前言          欢迎大家我分享和推荐好用的代码段~~ 声明          欢迎转载,但请保留文章原始出处:          CSDN:http://www.csdn.net        ...