San*_*kin 60 c undefined-behavior
我今天刚上课了 - 阅读C代码和输入,如果程序实际运行,所需答案就是屏幕上显示的内容.其中一个问题被声明a[4][4]为一个全局变量,并且在该程序的一个点上,它试图访问a[27][27],所以我回答了类似" 访问一个超出其边界的数组是一个未定义的行为 ",但老师说它a[27][27]的值将为0.
之后,我尝试了一些代码来检查"所有未初始化的golbal变量是否设置为0"是否为真.好吧,这似乎是真的.
所以现在我的问题是:
a[27][27]是0所有环境?编辑:
在该代码中,a[4][4]是唯一声明的全局变量,并且还有一些更多本地变量main().
我在DevC++中再次尝试了该代码.所有这些都是0.但在VSE中并非如此,其中大多数值都是,0但有些值具有Vyktor指出的随机值.
And*_*ico 50
你是对的:它是未定义的行为,你不能指望它总是生产0.
至于为什么在这种情况下你看到零:现代操作系统将内存分配给相对粗粒度的块中的进程,这些块称为比单个变量大得多的页面(在x86上至少为4KB).如果您有一个全局变量,它将位于页面的某个位置.假设a类型int[][]和ints是系统上的四个字节,a[27][27]将从开头位于大约500个字节a.因此,只要a靠近页面的开头,访问a[27][27]将由实际内存支持并读取它不会导致页面错误/访问冲突.
当然,你不能指望这一点.例如,如果a前面有大约4KB的其他全局变量,则a[27][27]不会被内存支持,并且当您尝试读取它时,您的进程将崩溃.
即使进程没有崩溃,也不能指望获取值0.如果你在现代多用户操作系统上有一个非常简单的程序,除了分配这个变量并打印该值之外什么都不做,你可能会看到0.在将内存移交给进程时,操作系统将内存内容设置为某些良性值(通常为全零),以便来自一个进程或用户的敏感数据不会泄漏到另一个进程或用户.
但是,没有一般保证您读取的任意内存为零.你可以在一个没有在分配时初始化内存的平台上运行你的程序,你会看到它上次使用时发生的任何值.
此外,如果a后面跟着足够的其他全局变量,这些变量被初始化为非零值,那么访问a[27][27]将显示您遇到的任何值.
Sha*_*our 28
访问数组越界是未定义的行为,这意味着结果是不可预知的,从而该结果a[27][27]存在0是不可靠的.
clang如果我们使用-fsanitize=undefined以下内容,请告诉您
runtime error: index 27 out of bounds for type 'int [4][4]'
Run Code Online (Sandbox Code Playgroud)
一旦你不确定的行为,编译器真的可以做任何事情,我们甚至还看到其中的例子gcc已经翻了有限循环进入一个无限循环基于围绕未定义行为的优化.双方clang并gcc在某些情况下可以产生和未定义的指令操作码,如果它检测未定义的行为.
为什么它是未定义的行为,为什么越界指针算术未定义的行为?提供了理由的总结.例如,结果指针可能不是有效地址,指针现在可以指向已分配的内存页面,您可以使用内存映射硬件而不是RAM等...
很可能存储静态变量的段比你正在分配的数组要大得多,或者你正在踩踏的段恰好被归零,所以你在这种情况下很幸运,但同样完全不可靠的行为.最有可能的是,您的页面大小为4k,并且访问权限a[27][27]在该范围内,这可能是您没有看到分段错误的原因.
标准说的是什么
在C99标准草案告诉我们这是第不确定的行为6.5.6 加法运算符覆盖指针算法是一个数组访问来自哪个下来.它说:
当一个具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型.如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式.
[...]
如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义.如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数.
并且未定义行为的标准定义告诉我们标准对行为没有要求,并且注意到可能的行为是不可预测的:
使用不可移植或错误的程序结构或错误数据时的行为,本国际标准不对此要求
注意可能的未定义行为包括完全忽略不可预测的结果,[...]
250*_*501 11
以下是标准的引用,它指定了什么是未定义的行为.
J.2未定义的行为
数组下标超出范围,即使一个对象显然可以使用给定的下标访问(如左边的表达式a [1] [7],给出声明int a [4] [5])(6.5.6).
将指针加到或减去数组对象和整数类型会产生一个指向数组对象之外的结果,并用作被计算的一元*运算符的操作数(6.5.6).
在您的情况下,您的数组下标完全在数组之外.取决于该值将为零是完全不可靠的.
此外,整个程序的行为是有问题的.
如果只是从visual studio 2012运行你的代码并获得这样的结果(每次运行时都不同):
Address of a: 00FB8130
Address of a[4][4]: 00FB8180
Address of a[27][27]: 00FB834C
Value of a[27][27]: 0
Address of a[1000][1000]: 00FBCF50
Value of a[1000][1000]: <<< Unhandled exception at 0x00FB3D8F in GlobalArray.exe:
0xC0000005: Access violation reading location 0x00FBCF50.
Run Code Online (Sandbox Code Playgroud)
当您查看模块窗口时,您会看到您的应用程序模块内存范围是00FA0000-00FBC000.除非你打开CRT支票,否则你的内存中没有任何东西会控制你做什么(只要你没有违反内存保护).
所以,你得0在a[27][27]纯属偶然.当您从位置00FB8130(a)打开内存视图时,您可能会看到如下内容:
0x00FB8130 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00FB8140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00FB8150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00FB8160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00FB8170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00FB8180 01 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
0x00FB8190 c0 90 45 00 b0 e9 45 00 00 00 00 00 00 00 00 00 À.E.°éE.........
0x00FB81A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00FB81B0 00 00 00 00 80 5c af 0f 00 00 00 00 00 00 00 00 ....€\¯.........
0x00FB81C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
..........
0x00FB8330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00FB8340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ <<<<
0x00FB8350 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
.......... ^^ ^^ ^^ ^^
Run Code Online (Sandbox Code Playgroud)
有可能你的编译器总是得到0代码,因为它使用内存,但只有几个字节,你可以找到另一个变量.
例如,上面显示的内存a[6][0]指向0x00FB8190包含整数值的地址4559040.
然后让你的老师解释这个.
我不知道这是否适用于你的系统,但是在使用a非零字节的数组之后使用blatting内存进行播放会产生不同的结果a[27][27].
在我的系统上,当我打印a[27][27]它的内容时0xFFFFFFFF.即转换为无符号的-1是以二进制补码设置的所有位.
#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;
}
Run Code Online (Sandbox Code Playgroud)
这是我的输出:
a[0][0] = 0
a[3][3] = 0
a[27][27] = 4294967295
Run Code Online (Sandbox Code Playgroud)
我在评论中看到了在Visual Studio中查看内存.最简单的方法是在代码中的某处添加断点(停止执行)然后进入Debug ... windows ... Memory菜单,选择例如Memory 1.然后找到阵列的内存地址a.在我的情况下,地址是0x0130EFC0.所以你输入0x0130EFC0地址fiend并按Enter键.这显示了该位置的内存.
比如我的情况.
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 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ
Run Code Online (Sandbox Code Playgroud)
零是数组a的过程,其字节大小为4 x 4 x sizeofint(在我的情况下为4)= 64字节.地址0x0130EFC0的字节数均为0xFF(来自b,c或d内容).
注意:
0x130EFC0 + 64 = 0x130EFC0 + 0x40 = 130F000
Run Code Online (Sandbox Code Playgroud)
这是ff你看到的所有那些字节的开头.可能是阵列b.
对于常见的编译器,访问超出其边界的数组只能在非常特殊的情况下提供可预测的结果,您不应该依赖它.示例:
int a[4][4];
int b[4][4];
Run Code Online (Sandbox Code Playgroud)
如果没有对齐问题,并且您既不要求积极优化也不要进行消毒检查,a[6][1]实际上应该是b[2][1].但请不要在生产代码中这样做!
| 归档时间: |
|
| 查看次数: |
3235 次 |
| 最近记录: |