在C语言中变量声明及其内存地址

8
我创建了一个简单的程序:
#include <stdio.h>

int main()
{
    int s1;
    int s2;
    int s3;
    
    int *p1, *p2, *p3;
    
    p1 = &s1;
    p2 = &s2;
    p3 = &s3;
    
    printf("%d\n%d\n%d", p1, p2, p3);
}

每次运行程序时,它都会打印指针p1、p2和p3的内存地址,有趣的是这些值之间的差异为12。我想知道背后的原因。为什么地址会相差12?
注意:每次执行程序都会发生这种情况。
输出:
我在许多类型的变量中测试了同样的程序,得到的结果是...
当变量为char类型时。
当变量为long类型时。
当我声明int数组时,每个数组的大小为1。
当第二个声明的数组大小为2时,它会得到额外的4字节偏移量。

1
为什么不使用专门用于打印指针的格式说明符,或者更好的是 std::cout?而且为什么不使用有效的 main 签名呢? - chris
6
这是在什么编译器和平台上? - Steve
1
这取决于编译器,正如大家所说。编译器可能随意放置任何内容。在发布版本中您可能看不到它。在这些变量之间可能存在调试信息,例如某种溢出检测或MSVC具有的在调试时实时编译等功能。您可以通过查看反汇编来查看其中包含的任何内容。 - Etherealone
3
我猜测是由于赋值的顺序,编译器选择交错存储intint*的值,而你在64位平台上。每个int占用4个字节,每个int*占用8个字节。但正如人们所说,看一下反汇编代码就知道了。 - paddy
5
我认为你也应该阅读关于正确的 main 函数签名以及 void main 的问题的内容。 - Iskar Jarak
显示剩余6条评论
2个回答

16

我猜这是一个调试版本。 我已经尝试过使用Visual Studio 2010构建的此程序。 在调试模式下,地址之间有12个字节差异。 在发布模式下,有4个字节(sizeof(int))的差异。

在调试版本中,MSVC编译器会添加额外的数据来帮助检测缓冲区溢出和未初始化内存的使用情况。 如果您在printf语句上设置断点并查看由p1指向的内存,则应该看到内存中的cc

内存初始化为许多不同的魔术值之一。cccccccc表示未初始化的堆栈空间。 有关更详细的列表,请参见此问题的答案:在Visual Studio C ++中,什么是内存分配表示?


5

我相当确定这是一种“编译器加入额外内容以检测您写入不应该写入的地方”的情况。微软喜欢这样做,这样它就可以检测您的代码是否存在问题。尝试使用以下方式:

void func()
{
  int x = 18;
  int *px = &x;
  px[1] = 4711;
  cout << "px = " << px << " x = " << x << " px[1] = " << px[1] << endl;
}

检查编译器是否无法“检测到”此代码正在执行不良操作......如果是,那是因为它在x和p之间放置了“填充”,并在函数返回时检查这些“填充”区域未被触摸。


gcc -g -O0 -Wall -fstack-protector-all -Wstack-protector op_code.c -o a.out 给出了 4 字节的偏移量,Windows 有哪些保护措施是 GCC 没有的呢? - 0x90
@0x90(或者我应该叫你NOP?):其中一条评论说OP正在使用Visual Studio 2008,因此没有使用gcc。 - Mats Petersson
@Steve,我也加了-g标志。 - 0x90
@0x90 我并不真正知道堆栈保护器是做什么的,或者它是如何工作的,我也不知道 MS 版本的细节。但我相信在某种意义上存在相似之处,即有额外的填充,并且在代码的某些点上检查这些填充字段以确保它们仍然正常。如果不正常,则会打印一些错误消息并停止执行。 - Mats Petersson
1
@0x90 很抱歉,我的gcc知识有限。但据我所知,-g标志会添加调试符号,以便您可以在调试器中查看某些级别的代码,而不是汇编程序。它不会改变代码。https://dev59.com/gG445IYBdhLWcg3wZJYn - Steve
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接