我使用此代码段:
// 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语言设计师没有像堆一样考虑这一点,除了留下未定义的行为
答案 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也是一个有用的工具。
答案 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,可以更彻底地检查堆栈溢出。其他语言可能有类似的选择。