2

我正在阅读linux上的标准输入。我提供读取的缓冲区长度不足(只有两个字符),缓冲区应该溢出并且应该发生分段错误。但是程序运行正常。为什么?

编译:

gcc file.c -ansi

运行:

echo abcd | ./a.out

程序:

#include<stdio.h>
#define STDIN 0

int main() {

    /* This buffer is intentionally too small for input */
    char * smallBuffer = (char *) malloc( sizeof(char) * 2 );

    int readedBytes;
    readedBytes = read(STDIN, smallBuffer, sizeof(char) * 4);

    printf("Readed: %i, String:'%s'\n", readedBytes, smallBuffer);

    return 0;
}

输出:

Readed: 4, String:'abcd'
4

6 回答 6

2

在这种情况下,期望出现分段错误通常是错误的。你看,缓冲区溢出会导致未定义的行为。这意味着此类代码的行为是不可预测的。它可能会或可能不会导致分段错误。

从技术上讲,例如,当您分配两个字节的缓冲区时,有两种可能的情况。

首先是在堆栈上分配缓冲区时。堆栈本身大于 2 个字节,如果您溢出该缓冲区,内存保护单元仍将允许您在该缓冲区“外部”的内存中写入。在这种情况下,您不会得到分段,但可能会弄乱堆栈“附近”存储的其他变量,这种情况通常称为“堆栈粉碎”。

第二种可能的情况是动态分配内存(即使用malloc())。在这种情况下,实际分配的缓冲区很可能更大,或者与之前分配/保留的内存放在同一页上。在这种情况下,程序将写入超过两个字节的缓冲区。它可能会或可能不会收到分段违规信号,但行为是未定义的。

有时,如果没有特别小心,这种情况很难找到。有一些工具可以帮助追踪类似的问题。例如, Valgrind就是其中之一。

附带说明一下,如果您确定您正在使用的虚拟地址无效或受到内存保护单元的读取、写入或执行保护(可能在您正在运行您的应用程序的硬件)。

希望能帮助到你。祝你好运!

于 2013-05-23T21:44:03.237 回答
1

在这种情况下,程序会覆盖它自己的一些内存。操作系统没有注意到这一点。

当进程试图访问不属于它的内存时,就会发生分段错误。但是,操作系统不是按字节分配内存块,而是分配更大的块 - 页面(例如,经常使用 4 KB 的大小)。因此,当您分配两个字节时,这两个字节由堆管理器放置在某个内存页面上(以前分配的或新的),并且整个内存页面被标记为属于您的进程。这两个字节很可能不会在内存页面的末尾结束,也就是说,您的程序将能够在这两个字节之后写入,而在写入时没有任何操作系统异常(但很可能它会向您触发之后)。

于 2013-05-23T21:48:17.620 回答
1

malloc保证至少为您提供所需的内存量。要查看错误,您可以使用 valgrind 等程序,您将看到以下内容:

 ==22265== Syscall param read(buf) points to unaddressable byte(s)
 ==22265==    at 0x4F188B0: __read_nocancel (syscall-template.S:82)
 ==22265==    by 0x4005B4: main (in /home/def/p/cm/Git/git/a.out)
 ==22265==  Address 0x51f1042 is 0 bytes after a block of size 2 alloc'd
 ==22265==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
 ==22265==    by 0x400595: main (in /home/def/p/cm/Git/git/a.out)
于 2013-05-23T21:39:17.960 回答
0

我认为你应该特别注意malloc()真正做了什么,malloc()在 linux 下调用它不仅不太可能失败,而且即使它返回一个积极的响应,它也不会给你真正的空间保留。

这种行为通常被称为“乐观内存分配策略”或“overcommit”,它与内核和 linux 下的 C 编程密切相关,这并不容易,在我看来你应该切换到 C++,你会发现一个熟悉的语法开始现在使用 C++ 来提高生产力比使用 C 更有意义,而且使用简单的 RAII 方法 C++ 比 C 更安全。

于 2013-05-23T21:52:34.670 回答
0

第三个参数不是缓冲区的大小,而是要读取的字节数。因此,您调用该函数并说“这是一个流,从中读取 4 个字节并放入此缓冲区”。但它不知道缓冲区大小(它只知道文件大小)。所以它会发生什么,它会尽可能多地读取并将其放入您的缓冲区中(假设您提供了一个足够大的缓冲区)。所以你得到的是内存损坏。在这种简单的情况下,您的程序可能工作正常,但通常它只是在其他地方意外失败。

于 2013-05-23T21:48:21.357 回答
0

缓冲区太小并不能保证程序会崩溃。这取决于缓冲区后面的字节中存在哪些数据、编译器如何安排可执行文件以及操作系统如何组织内存。可能是缓冲区后面的字节已经“属于”您的程序并且正在填充或以其他方式存储任何导入内容。

于 2013-05-23T21:38:36.357 回答