C语言 如果缓冲区的大小小于 nbyte,为什么它会起作用



代码是这样的:

#define BUFSIZ 5
#include <stdio.h>
#include <sys/syscall.h>
main()
{
    char buf[BUFSIZ];
    int n;
    n = read(0, buf, 10);
    printf("%d",n);
    printf("%s",buf);
    return 0;
}

然后我输入abcdefg,输出是:

8abcdefg

read(0, buf, 10);中,10大于5,即buf的大小。但这似乎并没有导致错误的结果。有人对此有想法吗?谢谢!

这是 C 语言中分配如何工作的怪癖。 您在堆栈上分配了一个缓冲区,它实际上只是您可以读取和写入的连续内存块。 允许您注销此数组末尾的事实意味着在这种情况下,它恰好可以工作。 也许在具有特定编译器和堆栈布局的机器上,您最终不会覆盖任何重要内容:-)

不建议依赖此行为在编译器版本之间相同。

原则上 1 可以读取和写入任何地址,但只有以有组织、定义明确的方式访问数据才是安全和有意义的。

内存分配(显式或隐式)的目的是使秩序混乱。声明 buf 数组时,堆栈上会保留一小块内存。
通常,分配具有一定的对齐方式(有时是一定的最小大小,操作系统也只能在非常粗略的级别上检测错误的访问),因此在分配的内存块和可以写入和读取的小区域之间通常会有小间隙,似乎没有"任何不好"的事情发生 - 但你应该假装事实并非如此, 您甚至不应该考虑使用这些实现细节来发挥自己的优势。

您的代码示例"有效",因为您不幸没有命中未分配或写保护的内存页面,并且您没有覆盖另一个会导致应用程序崩溃的重要堆栈值(例如函数的返回地址)。
我故意说"不走运",而不是"幸运",因为它似乎有效并不是一件好事。这是不正确的代码2,此类代码应该提前崩溃,因此您可以检测并修复问题。否则,它可能会导致很难诊断似乎发生在完全不相关的时间或地点的问题。即使它现在有效,你也不能保证它明天会工作(或者,在不同的计算机上,或者使用不同的编译器,或者使用稍微不同的代码)。

内存分配通常分为三个步骤。这是由 C 库完成的对操作系统的分配请求(通常与您的请求不直接对应),然后在库中完成一些簿记,以及您做出的承诺。在操作系统级别,页面级别上的实际物理分配在您首次访问内存时按需发生,假设 C 库之前已请求为访问位置分配。
在堆栈分配的情况下,这个过程在库级别要容易一些,因为它实际上只需要减少一个特殊的寄存器,但这与您无关。概念保持不变。

做出的承诺是,你只会从约定的区域读取或写入,这对你来说是最重要的。

您可能会违反承诺(故意或意外),但它仍然"有效",但这纯粹是巧合。
在堆栈上,您迟早会覆盖某些局部变量的存储(如果它们缓存在寄存器中,则可能无法检测到)和返回地址,这几乎肯定会在函数返回时导致崩溃(或类似的意外行为)。在堆上,您可以覆盖其他一些程序数据或访问尚未传达给操作系统作为保留的页面。在这种情况下,该程序将立即终止。

<小时 />1 我们暂时不要考虑虚拟内存和页面保护。
2 严格来说,这不是不正确的代码,而是调用未定义行为的代码。但是,在我看来,覆盖未分配的内存足够严重,值得贴上"不正确"的标签。

最新更新