我预计在执行下面的代码后会发生seg错误,但事实并非如此。有人能告诉我为什么吗?
int main(){
float *arr;
cout << arr[0] << "n" --> This prints out a ZERO. I am expecting a seg-fault.
cout << arr[1000] << "n" --> This gives me a seg-fault
return 0;
}
我想知道这是否是由于";"聪明";减轻崩溃的编译器的设计。但我不能确定。
由于指针arr
没有初始化,它可能具有以前使用时内存地址的任何值。
在您的情况下,使用该内存地址的代码以前可能使用该内存位置来存储指针,即存储指向有效对象的另一个内存地址。即使该对象的生存期同时过期,操作系统也可能无法检测到这一点,因为内存页可能没有返回到操作系统。因此,就操作系统而言,该内存页仍然是程序可读的(也可能是可写的(。这可能解释了为什么取消引用arr
的未初始化值不会产生分段错误。
表达式arr[1000]
将尝试取消引用与arr
的未初始化值相距4000字节的地址(假定sizeof(float)==4
(。内存页面的典型大小为4096字节。因此,假设arr
的未初始化值是指向4096字节的存储器页的开始附近的存储器地址,那么将4000添加到该地址将不会充分改变存储器地址以使地址指向不同的存储器页。然而,如果arr
的未初始化值是指向内存页中间某个位置的内存地址,则将4000添加到该地址将使其指向不同的内存页(假设内存页大小为4096字节(。这可能解释了为什么操作系统对两个地址的处理方式不同,这样一来,一次内存访问会导致分段故障,而另一次内存存取不会失败。
然而,这都是猜测(我经常使用"可能"一词就清楚地表明了这一点(。您的代码不会导致分段错误,这可能还有另一个原因。在任何情况下,当程序调用未定义的行为(通过取消引用未初始化的指针来实现(时,都不能依赖任何特定的行为。在某些平台上,它可能会导致分段故障,而在其他平台上,程序可能会完美工作。即使更改编译器设置(如优化级别(也可能足以更改程序的行为。
我想知道这是否是由于";"聪明";减轻崩溃的编译器的设计。
;"聪明";在这种情况下,应该报告某种错误(即崩溃(,而不是试图减轻崩溃。这是因为崩溃使bug更容易找到。
程序没有立即崩溃的原因是编译器和操作系统都没有检测到错误。
如果您希望更可靠地检测此类错误,那么您可能需要考虑使用某些编译器提供的功能来检测此类错误。例如,gcc和clang都支持AddressSanitizer。在这两个编译器上,您所要做的就是使用-fsanitize=address
命令行选项进行编译。然而,这将导致编译器添加额外的检查,这将显著降低性能(大约降低两倍(并增加内存使用率。因此,这只能用于调试目的。
我预计会发生seg故障。。。
您的程序有未定义的行为,因为指针arr
未初始化,并且您隐式地取消了对它的引用。
未定义的行为意味着任何1都可能发生,包括但不限于提供预期输出的程序。但是永远不要依赖(或根据未定义行为的程序的输出得出结论(。
所以您看到的输出(可能看到的(是未定义行为的结果。正如我所说,不要依赖于有UB的程序的输出。程序可能会崩溃。
例如,在这里程序没有崩溃,但在这里它崩溃了。
因此,使程序正确的第一步是删除UB然后并且只有到那时您才能开始对程序的输出进行推理。
1对于未定义行为的更准确的技术定义,请参阅此处,其中提到:对程序的行为没有限制