3 回答
TA贡献1900条经验 获得超5个赞
您说对了:这是未定义的行为,您不能指望它总是在产生0
。
至于为什么在这种情况下看到零的原因:现代操作系统将内存分配给进程中相对较粗的块(称为页面),这些块比单个变量大得多(在x86上至少为4KB)。当您有一个全局变量时,它将位于页面上的某个位置。假设a
类型为,int[][]
并且int
s为系统上的四个字节,a[27][27]
则从的开头开始约500个字节a
。因此,只要a
在页面的开头附近,访问a[27][27]
就会被实际的内存支持,并且读取它不会导致页面错误/访问冲突。
当然,您不能指望这一点。例如,如果a
前面有将近4KB的其他全局变量,那么a[27][27]
它将不由内存支持,并且当您尝试读取它时,过程将崩溃。
即使该过程没有崩溃,您也不能指望获得该值0
。如果您在现代的多用户操作系统上有一个非常简单的程序,除了分配该变量并打印该值之外什么也不做,您可能会看到0
。在将内存移交给进程时,操作系统会将内存内容设置为某个良性值(通常为全零),以使来自一个进程或用户的敏感数据不会泄漏到另一个进程或用户。
但是,不能完全保证读取的任意内存为零。您可以在未分配内存的情况下在平台上运行程序,并且会看到从上一次使用开始碰到的任何值。
同样,如果a
后面跟随着足够多的初始化为非零值的其他全局变量,则访问a[27][27]
将向您显示恰好在那里的任何值。
TA贡献1859条经验 获得超6个赞
访问数组越界是未定义的行为,这意味着结果是不可预知的,从而该结果a[27][27]存在0是不可靠的。
clang如果我们使用,可以很清楚地告诉你-fsanitize=undefined:
runtime error: index 27 out of bounds for type 'int [4][4]'
一旦你不确定的行为,编译器真的可以做任何事情,我们甚至还看到其中的例子gcc已经翻了有限循环进入一个无限循环基于围绕未定义行为的优化。双方clang并gcc在某些情况下可以产生和未定义的指令操作码,如果它检测未定义的行为。
为什么它是未定义的行为,为什么越界指针算术是未定义的行为?提供了很好的原因总结。例如,结果指针可能不是有效地址,该指针现在可以指向分配的内存页面之外,您可以使用内存映射的硬件而不是RAM等。
存储静态变量的段很可能比您正在分配的数组或要踩的段大得多,尽管刚好被清零,所以在这种情况下您很幸运,但行为又完全不可靠。您的页面大小很可能是4k,对的访问a[27][27]在该范围内,这可能就是为什么您没有看到分段错误的原因。
标准怎么说
在C99标准草案告诉我们这是第不确定的行为6.5.6 加法运算符覆盖指针算法是一个数组访问来自哪个下来。它说:
将具有整数类型的表达式添加到指针或从指针中减去时,结果将具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,以使结果数组元素和原始数组元素的下标之差等于整数表达式。
[...]
如果指针操作数和结果都指向同一数组对象的元素,或者指向数组对象的最后一个元素,则求值不会产生溢出;否则,行为是不确定的。如果结果指向数组对象的最后一个元素之后,则不应将其用作被评估的一元*运算符的操作数。
未定义行为的标准定义告诉我们,该标准对行为没有任何要求,并指出可能的行为是不可预测的:
在使用非便携式或错误程序构造或错误数据时的行为,对此国际标准不施加任何要求
注意可能的不确定行为范围包括完全忽略情况并产生不可预测的结果,[...]
TA贡献1757条经验 获得超8个赞
这是标准的引号,它指定什么是未定义的行为。
J.2不确定行为
即使显然可以使用给定下标访问对象,数组下标也超出范围(如在声明为int a [4] [5]的左值表达式a [1] [7]中)(6.5.6)。
将指针加或减到数组对象或整数类型中或超出数组对象和整数类型会产生指向数组对象正好超出数组对象的结果,并用作评估的一元*运算符的操作数(6.5.6)。
在您的情况下,数组下标完全在数组之外。完全依赖于该值将为零是不可靠的。
此外,整个程序的行为是有问题的。
- 3 回答
- 0 关注
- 462 浏览
添加回答
举报