为什么堆栈溢出/下溢不会触发运行时错误?

时间:2015-05-31 10:36:57

标签: c memory stack stackunderflow

我使用此代码段:

// stackoverflow.c 
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char** argv)
{
    int i;
    int a[10];
    // init
    a[-1] = -1;
    a[11] = 11;
    printf(" a[-1]= = %d, a[11] = %d\n", a[-1], a[11]);
    printf("I am finished.\n");
    return a[-1];
}

编译器是Linux x86的GCC。它运行良好,没有任何运行时错误。我还在Valgrind中测试了这段代码,它也没有触发任何内存错误。

$ gcc -O0 -g -o stack_overflow stack_overflow.c
$ ./stack_overflow
   a[-1]= = -1, a[11] = 11
   I am finished.

$ valgrind ./stack_overflow
==3705== Memcheck, a memory error detector
==3705== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==3705== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==3705== Command: ./stack_overflow
==3705==
   a[-1]= = -1, a[11] = 11
   I am finished.
==3705==
==3705== HEAP SUMMARY:
==3705==     in use at exit: 0 bytes in 0 blocks
==3705==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==3705==
==3705== All heap blocks were freed -- no leaks are possible
==3705==
==3705== For counts of detected and suppressed errors, rerun with: -v
==3705== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

根据我的理解,堆和堆栈是同一种内存。唯一的区别是它们在相反的方向上生长。

所以我的问题是:

为什么堆溢出/下溢会触发朗姆酒错误,而堆栈溢出/下溢不会?

为什么C语言设计师没有像堆一样考虑这一点,除了留下未定义的行为

6 个答案:

答案 0 :(得分:3)

valgrind未检测到堆栈缓冲区溢出。使用AddressSanitizer。至少需要gcc 4.8并且必须安装libasan。

gcc -g -fsanitize=address stackbufferoverflow.c

==1955==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x7fffff438d4c at pc 0x000000400a1d bp 0x7fffff438d10 sp 0x7fffff438d00
WRITE of size 4 at 0x7fffff438d4c thread T0
    #0 0x400a1c in main /home/m/stackbufferoverflow.c:9
    #1 0x7fe7e24e178f in __libc_start_main (/lib64/libc.so.6+0x2078f)
    #2 0x400888 in _start (/home/m/a.out+0x400888)

Address 0x7fffff438d4c is located in stack of thread T0 at offset 28 in frame
    #0 0x400965 in main /home/m/stackbufferoverflow.c:5

  This frame has 1 object(s):
    [32, 72) 'a' <== Memory access at offset 28 underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-underflow /home/m/stackbufferoverflow.c:9 main

答案 1 :(得分:0)

修改

这是一个有趣的tuto:

http://gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html

BTW Clang(OSX)检测到它,但它只是一个额外的功能,好的旧gcc会让你这么做。

ctest.c:6:5: warning: array index 42 is past the end of the array (which contains 1 element) [-Warray-bounds]
    a[42] = 42;
    ^ ~~
cpp.cpp:4:5: note: array 'a' declared here
    int a[1];
    ^
1 warning generated.

<强>旧

a[11] = 11;

会触发分段错误(但在这里它只有一个字节,它只是覆盖另一个变量的值,很可能),如果你想要堆栈溢出尝试进行无限递归的东西。

另外如果你想让你的代码段错误证明(仅适用于malloc),我建议你用电围栏编译它以进行测试。它将阻止程序超出其分配的内存(从第一个字节开始)

http://linux.die.net/man/3/efence

正如评论中所建议的那样,Valgrind也是一个有用的工具。

http://valgrind.org/

答案 2 :(得分:0)

C不会检查越界数组索引之类的内容。它只是做你告诉它的,在这种情况下,改变10个元素的数组中的元素号11。通常,这意味着您的程序会写入内存中应该存储此项目的位置(如果已存在)。这可能会也可能不会导致某种可见错误,例如崩溃。它可能没有任何效果,或者它可能使您的程序做一些奇怪的事情。这取决于什么,如果有的话,恰好存储在内存中的那个地方,以及如何使用它。

其他一些编程语言会执行这些检查,并保证报告错误。 C标准没有给出这样的保证,只是说它会导致“未定义的行为”。其中一个原因是应该可以在C中编写非常有效的程序,其中检查会导致一些小的,但在某些情况下可能是不可接受的延迟。此外,当设计C时,计算机速度较慢,而延迟则是一个更糟糕的问题。

在C中也无法保证会检测到或报告堆错误。 Valgrind不是C语言的一部分,而是一个不同的工具,它尽力使用其他更有效的机制来查找错误,但不能保证它会找到所有错误

答案 3 :(得分:0)

  

为什么C语言设计师没有像堆一样考虑这一点,除了留下未定义的行为

最初的C langauge设计师为自己编写了一种更舒适,更便携的汇编程序。原始语言的设计并不是针对程序员的防弹措施。错误。

如果您对相反的示例感兴趣,请查看Ada(http://en.wikipedia.org/wiki/Ada_%28programming_language%29)。

答案 4 :(得分:0)

  

为什么堆栈上溢/下溢不会触发运行时错误?

C不限于“堆”和“堆栈”实现。示例:main()中的变量不必位于“堆栈”中。甚至GCC也可能以无视简单理解的方式进行优化。 许多内存架构都是可能的。由于C未指定底层内存架构,因此以下仅是未定义的行为。 @Karoly Horvath

// Undefined behavior: accessing memory outside array's range.
int a[10];
a[-1] = -1;
a[11] = 11;

任何分析都可能在一周中的某一天使用给定的内存模型有意义,但这种行为只是众多可能中的一种。

答案 5 :(得分:0)

分配堆存储总是包括对内存不足的测试;对于堆栈空间,由于堆栈空间一次又一次地重复使用的方式,因此这不太重要。如果他们共享相同的存储块,那么他们可能会发生冲突。

GCC不会这样做,因为堆空间和堆栈空间是分开的;我不了解Valgrind。

在至少一种旧语言(Turbo C)中,如果堆栈顶部和堆栈底部之间保留的存储空间少于256字节,则alloc()将失败。假设256个字节足以容纳堆栈增长。如果不是,则会出现一些非常奇怪的运行时错误。

Turbo C有一个编译时选项-N,​​可以更彻底地检查堆栈溢出。其他语言可能有类似的选择。