C 访问全局数组是否超出其绑定的未定义行为?

C 访问全局数组是否超出其绑定的未定义行为?,c,undefined-behavior,C,Undefined Behavior,我今天刚在班上做了一个考试——阅读C代码和输入,要求的答案是如果程序真的运行,屏幕上会出现什么。其中一个问题将a[4][4]声明为一个全局变量,在该程序的某一点上,它试图访问a[27][27],因此我的回答类似于“”,但老师说a[27][27]的值为0 之后,我尝试检查“所有未初始化的golbal变量都设置为0”是否为真。嗯,这似乎是真的 现在我的问题是: 似乎已经清除了一些额外的内存,并为代码运行保留了一些内存。保留了多少内存?为什么编译器要保留比它应该保留的更多的内存,它的用途是什么 对于

我今天刚在班上做了一个考试——阅读C代码和输入,要求的答案是如果程序真的运行,屏幕上会出现什么。其中一个问题将
a[4][4]
声明为一个全局变量,在该程序的某一点上,它试图访问
a[27][27]
,因此我的回答类似于“”,但老师说
a[27][27]
的值为
0

之后,我尝试检查“所有未初始化的golbal变量都设置为
0
”是否为真。嗯,这似乎是真的

现在我的问题是:

  • 似乎已经清除了一些额外的内存,并为代码运行保留了一些内存。保留了多少内存?为什么编译器要保留比它应该保留的更多的内存,它的用途是什么
  • 对于所有环境,
    a[27][27]
    是否为
    0
编辑:

在该代码中,
a[4][4]
是声明的唯一的全局变量,在
main()
中还有一些局部变量


我在DevC++中再次尝试。它们都是
0
。但在VSE中并非如此,在VSE中,大多数值都是
0
,但正如Vyktor所指出的,有些值是随机值。

访问数组超出范围是未定义的行为,这意味着结果是不可预测的,因此
a[27][27]
成为
0
的结果根本不可靠

clang
如果我们使用
-fsanize=undefined
,请非常清楚地告诉您这一点:

runtime error: index 27 out of bounds for type 'int [4][4]'
一旦有了未定义的行为,编译器就真的可以做任何事情,我们甚至看到了
gcc
基于未定义行为的优化的例子。如果检测到未定义的行为,在某些情况下,
clang
gcc
都可以

为什么是未定义的行为,提供了一个很好的原因总结。例如,生成的指针可能不是有效地址,指针现在可能指向分配的内存页之外,您可以使用内存映射硬件而不是RAM等

最有可能的是,存储静态变量的段比正在分配的数组或正在踩踏的段大得多,尽管恰好被调零了,所以在这种情况下,您很幸运,但同样是完全不可靠的行为。您对
a[27][27]
的访问很可能在该范围内,这可能就是您没有看到分段错误的原因

标准怎么说

6.5.6
加法运算符一节中告诉我们这是未定义的行为,该节介绍了数组访问的指针算法。它说:

将具有整数类型的表达式添加到或减去时 对于指针,结果具有指针操作数的类型。如果 指针操作数指向数组对象的元素,数组 如果足够大,则结果将指向与 原始元素,例如 结果和原始数组元素等于整数表达式

[……]

如果指针操作数和结果都指向 相同的数组对象,或数组对象的最后一个元素后一个, 评估不得产生溢出;否则,行为 没有定义。如果结果指向最后一个元素的上方 数组对象,它不能用作一元数的操作数* 要计算的运算符

标准对未定义行为的定义告诉我们,标准对行为没有任何要求,并指出可能的行为是不可预测的:

使用不可移植或错误的程序结构或 本国际标准没有规定的错误数据 要求

注意:可能的未定义行为包括忽略情况 结果完全不可预测,[…]


你是对的:这是一种未定义的行为,你无法计算它总是产生
0

至于为什么在本例中看到零:现代操作系统将内存分配给相对粗粒度的块(称为页面)中的进程,这些块比单个变量大得多(x86上至少4KB)。当您有一个全局变量时,它将位于页面上的某个位置。假设
a
的类型为
int[][]
并且
int
s是系统上的四个字节,
a[27][27]
将位于距离
a
开头约500字节的位置。因此,只要
a
接近页面的开头,访问
a[27][27]
将得到实际内存的支持,读取它不会导致页面错误/访问冲突

当然,你不能指望这一点。例如,如果
a
前面有近4KB的其他全局变量,则
a[27][27]
将不受内存支持,当您尝试读取它时,进程将崩溃

即使进程没有崩溃,也不能指望获得值
0
。如果在现代多用户操作系统上有一个非常简单的程序,它只分配这个变量并打印那个值,那么您可能会看到
0
。操作系统在将内存移交给进程时,会将内存内容设置为某个良性值(通常为全零),以便一个进程或用户的敏感数据不会泄漏到另一个进程或用户

但是,并不能保证您读取的任意内存为零。您可以在内存分配时未初始化的平台上运行程序,您将看到上次使用时出现的任何值

此外,如果
a
后面有足够多的初始化为非零值的其他全局变量,则访问
a[27][27]
int a[4][4];
int b[4][4];
#include <stdio.h>
#include <string.h>

#define printer(expr) { printf(#expr" = %u\n", expr); }

   unsigned int d[8096];
   int a[4][4];  /* assuming an int is 4 bytes, next 4 x 4 x 4 bytes will be initialised to zero */
   unsigned int b[8096];
   unsigned int c[8096];


int main() {

   /* make sure next bytes do not contain zero'd bytes */
   memset(b, -1, 8096*4);
   memset(c, -1, 8096*4);
   memset(d, -1, 8096*4);

   /* lets check normal access */
   printer(a[0][0]);
   printer(a[3][3]);

   /* Now we disrepect the machine - undefined behaviour shall result */
   printer(a[27][27]);

   return 0;
}
a[0][0] = 0
a[3][3] = 0
a[27][27] = 4294967295
0x0130EFC0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ..................................
0x0130EFE2  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ff ff  ..............................ÿÿÿÿ
0x0130F004  ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ 
0x0130F026  ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
0x0130F048  ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
0x130EFC0 + 64 = 0x130EFC0 + 0x40 = 130F000
push ebp      ;save register on the stack
mov ebp,esp   ;get actual stack address
sub esp,4     ;displace the stack of 4 bytes that will be used to store a 4 chars array
0x0.....1C   [Parameters (if any)]    ;former function
0x0.....18   [Return Address]
0x0.....14   EBP
0x0.....10   0x0......x               ;Local DWORD parameter
0x0.....0C   [Parameters (if any)]    ;our function
0x0.....08   [Return Address]
0x0.....04   EBP
0x0.....00   0, 'c', 'b', 'a'    ;our string of 3 chars plus final nul