为什么不会发生分段错误?

Why does a segmentation fault not occur?

我预计在执行下面的代码后会发生段错误,但事实并非如此。谁能告诉我为什么?

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 未初始化,它可能具有该内存地址先前使用时的任何值。

在您的情况下,之前使用该内存地址的代码可能使用该内存地址来存储指针,即用于存储指向有效对象的另一个内存地址。即使 lifetime of that object has expired in the mean time, the operating system will probably not be able to detect this, because the memory page 可能没有返回给操作系统。因此,就操作系统而言,该内存页仍可由程序读取(也可能是可写的)。这可能解释了为什么取消引用 arr 的未初始化值不会产生分段错误。

表达式 arr[1000] 将尝试取消引用与 arr 的未初始化值相距 4000 字节的地址(假设 sizeof(float)==4)。内存页的典型大小为 4096 字节。因此,假设 arr 的未初始化值是一个内存地址,它指向一个 4096 字节内存页的开始附近,那么向该地址添加 4000 不会改变内存地址足以使地址指向一个不同的内存页。但是,如果 arr 的未初始化值是指向内存页中间某处的内存地址,那么在该地址上加上 4000 将使其指向不同的内存页(假设内存页大小为 4096字节)。这可能解释了为什么您的操作系统以不同方式处理这两个地址,因此一个内存访问会导致分段错误而另一个内存访问不会失败。

然而,这都是猜测(我经常使用“可能”这个词就表明了这一点)。您的代码不会导致分段错误的另一个原因可能是。无论如何,当您的程序调用未定义的行为(它通过取消引用未初始化的指针来实现)时,您不能依赖任何特定的行为。在某些平台上,它可能会导致分段错误,而在其他平台上,该程序可能会完美运行。即使更改编译器设置(例如优化级别)也可能足以改变程序的行为。


I am wondering if this due to the "smart" design of the compiler that alleviates the crash.

在这种情况下,“明智”的做法是报告某种错误(即崩溃),而不是试图缓解崩溃。这是因为崩溃使错误更容易找到。

您的程序没有立即崩溃的原因是您的编译器和操作系统都没有检测到错误。

如果您希望更可靠地检测到此类错误,那么您可能需要考虑使用一些编译器提供的功能来尝试检测此类错误。比如gcc和clang都支持AddressSanitizer。在这两个编译器上,您所要做的就是使用 -fsanitize=address command-line 选项进行编译。然而,这将导致编译器添加额外的检查,这将显着降低性能(大约两倍)并增加内存使用。因此,这只能用于调试目的。

I am expecting a seg-fault to occurr...

您的程序有 未定义的行为,因为指针 arr 未初始化并且您正在隐式取消引用它。

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.

所以您看到(也许看到)的输出是未定义行为的结果。正如我所说,不要依赖具有 UB 的程序的输出。程序可能会崩溃。

例如,here the program doesn't crash but here它崩溃了。

因此,使程序正确的第一步是删除 UB。 然后并且只有那时你可以开始对程序的输出进行推理。


1有关未定义行为的技术上更准确的定义,请参阅 this 其中提到:没有对程序行为的限制.