【问题标题】:C read reallocates bufferC读取重新分配缓冲区
【发布时间】:2013-05-24 05:52:36
【问题描述】:

我正在阅读 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'

【问题讨论】:

  • 您不需要在 C 程序中强制转换 malloc() 的返回值。而sizeof(char)1
  • 您碰巧碰到了仍然属于您的进程的内存。您可能会遇到可怕的运行时错误,因为其他变量会在真实代码中被覆盖。这是未定义的行为。

标签: c io


【解决方案1】:

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

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

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

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

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

附带说明,如果您确定您使用的虚拟地址无效或受到memory protection unit(可能不存在完全在您运行应用程序的硬件上)。

希望对您有所帮助。祝你好运!

【讨论】:

    【解决方案2】:

    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)
    

    【讨论】:

    • malloc 什么都不授予,尤其是在 linux 下,请参阅“乐观内存分配策略”或“overcommit”主题。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-03-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多