0

我使用这个代码片段:

// 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];
}

编译器是 GCC for linux x86。它运行良好,没有任何运行时错误。我还在 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)

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

所以我的问题是:

为什么堆上溢/下溢会触发 rum-time 错误,而堆栈上溢/下溢不会?

为什么 C 语言设计器没有像堆一样考虑到这一点,而是将其保留为未定义行为

4

6 回答 6

4

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
于 2015-05-31T11:30:24.647 回答
0

编辑

这是一个有趣的教程:

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/

于 2015-05-31T10:49:30.963 回答
0

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

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

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

于 2015-05-31T10:52:41.977 回答
0

为什么 C 语言设计器没有像堆一样考虑到这一点,而是将其保留为未定义行为

最初的 C 语言设计者为自己编写了一种更舒适、更便携的汇编程序。原始语言的设计并不是为了防止程序员的错误。

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

于 2015-05-31T10:55:24.520 回答
0

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

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

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

任何分析都可能对一周中给定日期的给定内存模型有意义,但这种行为只是众多可能性之一。

于 2015-05-31T22:17:34.200 回答
0

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

GCC 不会这样做,因为堆空间和堆栈空间是分开的;我不知道瓦尔格林德。

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

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

于 2017-11-08T15:09:23.047 回答