• 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
    ”, 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
    ",__func_version__,b,&a);
    /*
    	for(int i=0;i<100;++i){
    		printf("%d + %s = %s
    ", i, b, i+b);
    	}
    */
    	printf("%d + %s = %s
    ", 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非常easy的我们能够加入例如以下代码:

    #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
    ",foo_v.__func_version__,foo_v.b);
    	printf("%d + %s = %s
    ",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
    ",__func_version__,b,&a);
    	printf("%d + %s = %s
    ", 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啥的光死看书不中啊,要学以致用啊,在此抛砖引玉。谢谢各位欣赏哦。

    大笑

  • 相关阅读:
    linux字符设备文件的打开操作
    Linux用ps命令查找进程PID再用kill命令终止进程的方法
    Linux内核锁与中断处理
    写给大数据开发初学者的话
    zabbix监控系统客户端安装
    详解zabbix安装部署(Server端篇)
    Keepalived+Nginx架构整理版
    Nginx + Tomcat 动静分离实现负载均衡
    五个常用的Linux监控脚本代码
    16个Linux服务器监控命令
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5156538.html
Copyright © 2020-2023  润新知