4

请告诉我关于以下代码的堆栈和堆之间的区别

int main()
{
    int arr[3];
    int *a;

    arr [5] = 6; // out of bound but it will not give error.
    arr [3000] = 8 ; //SIGSEGV 

    a = malloc (sizeof (int));
    a[4] = 6; 
    a[4000] = 8; //No error
}

我知道 arr 是一个静态数组,当我执行 arr[3000] 时,我正在访问其他进程的地址,这会给出 SIGSEGV 错误。但我不明白为什么 a[4000] 不会给我任何运行时错误,即 SIGSEGV 信号。

谢谢

4

6 回答 6

9

不能保证这些调用中的任何一个实际上都会写入不可访问的内存(这会触发段错误)。程序很可能有权写入该内存并且不会触发段错误,但会覆盖与您的数组无关的其他一些内部数据。这当然可能会在程序的其他地方导致意想不到的影响。

通常这称为未定义行为。当你写出数组的界限时,没有任何承诺会发生什么,任何事情都可能发生,也可能不发生。

于 2012-01-23T18:47:57.997 回答
3

引用数组边界之外的项目是未定义的行为,这意味着任何事情都可能发生(异常、无异常或其他)。arr[5]赋值没有导致错误的原因可能是因为该值仍在有效的堆栈空间内(但它可能会导致稍后在运行时间较长的应用程序中出错)。对已分配数组的无效分配可能导致写入属于该进程的内存中的页面,因此不会导致错误。但这可能会因跑步而改变。即使该地址属于进程地址空间之外的页面,它也取决于操作系统实际发生的情况。

于 2012-01-23T18:45:16.390 回答
1

堆是您从中 malloc() 内存块的内存。

一[4000] = 8; 没有失败,因为它没有落入其他进程的内存地址是幸运的。这只是偶然

于 2012-01-23T18:42:52.100 回答
1

缓冲区溢出是未定义的行为。如果缓冲区溢出在堆栈上,则可能在星期一崩溃,如果在堆上,则在星期二崩溃。它们只是未定义的行为。

这是 C 段落,它说它是未定义的行为:

(C99,6.5.6p8)“如果结果指向数组对象的最后一个元素,则它不应用作被评估的一元 * 运算符的操作数。”

当然 a[]是一个伪装的一元运算*符:

(6.5.2.1p2) “下标运算符 [] 的定义是 E1[E2] 等同于 (*((E1)+(E2)))。”

于 2012-01-23T18:52:49.390 回答
1

您突出显示的所有案例都代表“未定义的行为”。

在某些情况下,这是一个 noop,而在其他情况下,这是一个分段错误。

“未定义的行为”特别糟糕的是,它可能会按预期工作一段时间,然后突然开始产生“不希望的副作用”(即分段错误)。这使得在生产中调试和重现这些条件变得非常困难。

于 2012-01-23T18:46:40.707 回答
1
int main()
{
    int arr[3];
    int *a;

    arr [5] = 6; // out of bound but it will not give error.
// J: False - it is undefined. expect raptors, or something.

    arr [3000] = 8 ; //SEGSEV
// J: Now you see the effects of undefined behavior, even though you did not in a previous invalid access.

    a = malloc (sizeof (int));
    a[4] = 6; // J: Still undefined behavior
    a[4000] = 8; //No error
// J: Still undefined behavior
}

但我不明白为什么 a[4000] 不会给我任何运行时错误,即。segsev 信号。

它将在另一个平台或架构上。这真的没关系 - 你必须始终避免 UB。

无论如何,不​​同之处在于系统分配器的实现(假设编译器没有将 malloc 的结果放在堆栈上)。

你的分配器如何管理和分配内存是一个你不应该依赖的实现细节,尤其是当你扔 UB 时。

分配器可以从更大的物理分配中分配内存。此底层实现因平台而异。

于 2012-01-23T18:49:16.693 回答