C内存布局相关问题

如下代码输出什么:

#include<stdio.h>
int main()
{
    char a=0;
    char b=0;
   
    int *p=(int *)&b;
    *p=258;
    printf("%d %d",a,b);
    return 0;
}

在看国外一本linux方面的书,里面讲到c语言内存布局相关,书中说这个程序运行结果是1 2,可是我再我的电脑(mac)上运行时02,并且想不明白这是为什么.
我目前仅有的思路如下:
258的二进制形式100000010
指针p指向了字符b的起始地址,字符与整形的地址对比可以通过下面的代码得出:

#include <stdio.h>
int main()
{
    char a = 0;
    char b = 0;
    char c = 0;
    int  d = 0;
    int  e = 0;
    int  f = 0;
    printf("%p\n",&a);
    printf("%p\n",&b);
    printf("%p\n",&c);
    printf("%p\n",&d);
    printf("%p\n",&e);
    printf("%p\n",&f);
    return 0;
}

代码输出
0x7fffc38df0df
0x7fffc38df0de
0x7fffc38df0dd
0x7fffc38df0d8
0x7fffc38df0d4
0x7fffc38df0d0
地址由高到低,所以我的理解是*p复制258(258的二进制形式100000010)会去覆盖字符a b 原有的值,所以b的值是00000010 a的值是000000001,a的值是1,b的值是2.此时涉及到大端与小端方式,我通过如下程序判断出我电脑环境为小端方式:

#include <stdio.h>
#include <string.h>
union u_tag
{
    short  s;
    char   c[sizeof(short)];
}un;

int main(int argc, const char * argv[])
{
    un.s = 0x0102;
    //打印数组c中地址关系
    printf("%p  %p\n",&un.c[0],&un.c[1]);
    if (sizeof(short) == 2) {
        if (un.c[0] == 1 && un.c[1] == 2)
            printf("big-endian\n");
        else if (un.c[0] == 2 && un.c[1] == 1)
            printf("little-endian\n");
        else
            printf("unknown\n");
    }
    else
    {
        printf("sizeof(short) = %lu\n", sizeof(short));
    }
    
    return 0;

}

可是我实际运行中确实0 2.
图片描述

阅读 3.8k
5 个回答

估计你开了编译器优化。

我的环境:

  • Ubuntu 16.04 (64 bit)

  • gcc 5.4.0

不开优化直接编译,结果的确是 1 2;但一旦开了优化(-O1, -O2, -O3, -Og结果都一样,其中 -Og 会报错),结果就变成了 0 2。原因在于:编译器在编译阶段就已经将值算好了,你输出的其实是常量。

-O1 优化的汇编代码举例(使用 gcc test.c -O1 -S 获得,这里只贴核心部分):

    movl    $2, %ecx
    movl    $0, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    movl    $0, %eax
    call    __printf_chk
    movl    $0, %eax

%edi, %esi, %edx, %ecx 分别是 __printf_chk 的参数,%esi 指向 %s %s 的地址。

可以发现最后两个参数并没有访问栈区,而是直接使用了立即数 0 和 2,可见这两个值的计算在编译期就已经完成了。

图片描述

没毛病

理论上的结果是 1 2。

但是操作的内存是栈,这里涉及到栈内存检测的问题。输出 0 2 是因为运行时栈异常处理所致(a 保留原值)。
为了更好的展现原因下面首先贴 Visual Studio 的调试及选项信息。

图1,关闭栈检测,此时输出的是理论结果:1 2
图片描述

图2,默认情况下(开启栈检测),结果是 0 2,并且报异常。这是默认情况下的结果。
图片描述

图3 异常类型,图4 检查选项,影响输出的原因,栈检测选项。
异常
检测选项

这个异常在VS里是 “Run-Time Check Failure #2”。因为 VS 默认开启了栈检测,对栈内存的 “非法” 操作由异常接管,a 部分的改变未执行(保留原值)。所以默认情况下输出 0 2。

楼主使用的 GCC 也有类似的设定,当O1以上优化选项开启时便会开启栈内存的相关检查,不同版本的 GCC 的默认选项有差别。下面是 GCC 官方文档说明。

优化选项(O 会开启大量选项包括栈检测):
https://gcc.gnu.org/onlinedoc...

更多的 GCC 章节
https://gcc.gnu.org/onlinedoc...

其实你的那个对大小端的判断是错误的。
Intel x86的CPU是小端的,这个毋庸置疑。除非你的Mac电脑是十几年前的老古董,用的PowerPC的CPU。

一,请贴上你uname -a的结果。
二,为了避免多写的字节(实际写了4个字节),破坏栈帧。你在最前面在加入两个char变量。再试试。

另外注意,C语言标准并没有规定局部变量的内存顺序。栈只是一种常见的实现。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进