1

代码如下:

#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。但这似乎并没有导致错误的结果..有人对此有想法吗?谢谢!

4

2 回答 2

1

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

不建议依赖编译器版本之间的这种行为相同。

于 2013-11-15T08:04:28.057 回答
1

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

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

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

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

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

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


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

于 2013-11-15T09:54:08.540 回答