为什么这段代码没有导致段错误?

3
为什么这段代码没有造成段错误?
#include <stdio.h>
int main()
{
    int i;
    int arr[] = {1, 2, 3, 4};

    for(i=0;i<8;i++)
    {
        arr[i] = i;
        printf(" %d", arr[i]);
    }

    printf("\n");

    return 0;
}

当我在for循环中用9代替8时,就会出现问题。

注意:我在32位的CrunchBang Linux上尝试这个。


9
读取超出数组末尾的内容属于未定义行为。未定义行为可以是任何结果,不一定会导致段错误。 - Cairnarvon
2
纯属运气。它打印了一些垃圾数字,不是吗? - Humungus
又一个过于本地化的骑手... :'( - user529758
3个回答

14

技术上讲,这个程序会导致未定义行为,这意味着这个程序可以做任何事情而没有任何保证。它可能在原则上格式化您的硬盘,向所有朋友发送恶意邮件,点燃您的计算机或变得有感知并奴役人类。

在这种情况下,当n = 8时的未定义行为没发生什么坏事,而当n = 9时的未定义行为造成了段错误。两者在程序中完全合法,但都不能保证可移植性。

希望这能帮到您!


2
@templatetypedef是正确的;他比我先回答了。未定义行为不一定意味着段错误。
但我想提供一些关于为什么 n=9 时会出现段错误而 n=8 不会的猜测。通常,像 int iint arr[] 这样的变量驻留在上,它向下增长。所以,i 可能驻留在地址 0x4000,如果它是一个 4 字节的 int,那么 arr[0]0x4004arr[1]0x4008,以此类推。在这种情况下,编译器很可能已经为 arr 分配了 16 个字节,并且它可能正在使用低于 0x4014(即 arr 后面的第一个字节,即 arr[5] 的地址)的地址。但是,除了您手动声明的变量之外,栈上通常还有其他东西。例如,printf 调用的参数可能在栈上,还可能有其他的簿记信息。因此,如果 arr[9] 恰好与编译器用于其他某些内容的栈位置重合,您将意外地破坏该信息,这可能导致段错误。
另外,如果arr [8]在从操作系统分配的堆栈帧底部,则您的操作系统将配置处理器以拒绝执行任何指令,从与arr [9]重合的地址加载或存储值。我认为这种情况不太可能发生,因为堆栈大小通常为4KB左右,对于这样一个短程序,您应该远未接近分配的堆栈的末尾。

虽然@templatetypedef正确地说了未定义的行为,但我正在寻找像这样的分析。 在循环中执行时,arr应该位于堆栈顶部。因此,它不会破坏任何其他成员。printf的参数和任何其他簿记将转到更高的地址,例如arr[5]或arr[9]的地址,或者基于编译器而言更高。当调用printf时,它可能会覆盖arr[5]之后的某些值。但是,这不应影响printf的执行。从printf返回时,arr再次位于堆栈顶部。 - Siddique

0

templatetypedef的回答是正确的,这是一种未定义行为的情况(这并不像你想象的那样罕见),但我想补充一些内容:

9*sizeof(int)不是2的幂次方。当编译器分配内存时,它们通常会按照2的幂次方字节进行分配,以防止碎片化。

你可能会想知道为什么它没有分配4*sizeof(int),因为那正好是你所请求的。我不确定,但可能是编译器分配了一些额外的字节,或者它有一个最小的内存量来为数组分配内存。这取决于编译器和你编译代码时使用的选项。

尝试在没有优化的情况下运行你的代码(在命令行上使用-O0),它可能会分配你需要的确切内存量,并且在i>=4时会导致段错误。

尽管我可能错了,但我不确定C编译器是在堆还是栈中分配静态数组。


你确定这个数组是在堆上分配的吗?我会认为以这种方式声明的数组会在栈上分配。 - templatetypedef
@templatetypedef 在C/C++中,包括固定大小数组(如arr)在内的局部变量通常分配在堆栈上。 - Nicu Stiurca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接