如何让gets()函数超出由malloc()分配的内存?

3

我对malloc和realloc函数有些疑惑。当我使用malloc函数为字符指针分配10个字节的内存时,但当我尝试为该字符指针分配值时,如果我赋值的内容超过10个字节,它应该不可能成功吧。

例如:

main()
{
    char *ptr;
    ptr=malloc(10*sizeof(char));
    gets("%s",ptr);
    printf("The String is :%s",ptr);
}

样例输出:

$./a.out
hello world this is for testing

字符串为:hello world this is for testing

现在看一下输出,字符数超过了10个字节。这是怎么可能的?我需要一个清晰的解释。谢谢!


1
这是关于 gets 的问题,不是关于 malloc 的。 - Nathan Fellman
1
这将导致未定义的行为。这可能可以工作,也可能不起作用,宇宙都有可能融化。 - SBI
@Nathan Fellman 我使用了 malloc 函数分配了 10 字节的内存,但实际上获取的内存超过了 10 字节。为什么? - Q_SaD
请不要在C语言中将malloc()的返回值强制转换。 - unwind
1
gets("%s",ptr); 不是对 gets 函数的有效调用。gets 的声明为 char *gets(char *);,它只接受一个参数,即指向缓冲区的指针。问题中的代码是否为实际编译和执行的代码? - Eric Postpischil
显示剩余2条评论
5个回答

6
这就是为什么使用gets是一件邪恶的事情。请改用fgets

malloc与此无关

不要使用gets()

诚然,解释一下可能会有用,对吧?首先,gets()不允许您指定缓存区的长度来存储字符串,这将允许人们在超出缓冲区末尾之后继续输入数据,相信我,这是一个坏消息。

详细说明:

首先,让我们看一下该函数的原型:

#include <stdio.h>
char *gets(char *s);

您可以看到,唯一的参数是一个char指针。那么,如果我们像这样创建一个数组:

char buf[100];

我们可以这样将其传递给 gets()
gets(buf)

到目前为止,看起来一切都很好。但实际上,我们的问题已经开始了。 gets() 只收到了数组的名称(指针),它不知道数组的大小,而且仅凭指针无法确定大小。当用户输入文本时,gets() 会将所有可用数据读入数组中,如果用户明智地输入少于99个字节,则这样做没有问题。然而,如果他们输入的数据超过99个字节,gets() 将不会停止在数组的末尾处写入数据。相反,它会继续向后写入,并占用未分配的内存。
  • 这个问题可能表现出以下几种方式:

  • 没有任何可见影响

  • 立即程序崩溃

  • 在程序生命周期的某个时间点结束(可能是1秒后,也可能是15天后)

  • 结束另一个无关的程序

  • 错误的程序行为和/或计算......等等。这就是"缓冲区溢出"错误的问题,你永远无法预测它们何时以何种方式袭击你。


1
你遗漏了最重要的影响:7. 任意代码执行。 - ninjalj

2

2

malloc是为您保留内存的函数。规则是您可以使用通过这种方式分配的内存和其他方式(例如定义自动或静态对象)分配的内存,但不允许您使用未为您分配的内存。

然而,malloc和操作系统并没有完全执行这些规则。遵守这些规则的责任在于您,而不是malloc或操作系统。

通用操作系统具有内存保护功能,防止一个进程未经允许读取或更改另一个进程的内存。但它无法防止一个进程以不当方式读取或更改其自身的内存。当您访问不应访问的字节时,没有总是能够防止此类情况的机制。内存存在,您可以访问它,但是您不应该这样做。

gets是一个设计不良的函数,如果输入行足够长,则会写入任意量的内存。这意味着您无法防止它写入比您分配的内存更多的内存。您应该使用fgets替代它,它具有限制它可以写入的内存量的参数。

通用操作系统以页面为单位分配内存块。页面的大小可能为4096字节。当malloc为您分配内存时,它可以从操作系统获取的最小尺寸是一个页面。当您请求十个字节时,malloc将获取一个页面(如果需要),选择其中的十个字节,记录已分配了页面的一小部分但其余部分可供其他用途,并向您返回指向这十个字节的指针。当您进行进一步的分配时,malloc可能会从同一页中分配额外的字节。

当您超出已分配给您的字节时,您正在违反规则。如果您的进程中没有其他部分使用这些字节,则您可能会逃脱此次违规行为。但是您可能会更改malloc用于跟踪分配的数据,更改属于单独内存分配的数据,或者如果您足够进一步,则更改完全不同的程序部分正在使用的位于另一页中的数据。

通用操作系统确实提供了一些保护措施来防止您在进程内不正确地使用内存。如果您尝试访问未映射到虚拟地址空间中的页面,或者尝试修改标记为只读的页面,则会触发故障并中断您的进程。但是,此保护仅适用于页面级别,并且无法防止您不正确地使用分配给您的内存。


1
malloc函数将保留10个字节(在您的情况下,假设char占用1个字节),并返回保留区域的起始点。
您使用gets命令,因此它会获取您输入的文本,并使用您的指针将其写入。 Windows/Mac OS X/Unix(高级操作系统)具有受保护的内存。 这意味着,当您执行malloc/new时,操作系统会为您的程序保留该内存区域。如果另一个程序试图向该区域写入,则会引发段错误,因为您在不应写入的区域上进行了写作。
您保留了10个字节。 如果字节11、12、13和14尚未预留给另一个程序,则不会崩溃;否则,您的程序将访问受保护的区域并崩溃。

这是一个有用的答案,分配的字节已经被保留,可以通过该指针访问空闲块。当它被保留时,我们无法访问它。 - Q_SaD
2
在Windows、OS X和Unix中,除非特别安排共享内存,否则另一个进程甚至无法尝试写入此进程的内存区域(因为它们具有独立的虚拟地址空间)。这意味着其他进程甚至没有与当前进程中的内存区域相对应的地址。缓冲区溢出有时不会导致可观察到的故障的实际原因是,当前进程中的其他实体(库例程、其他程序例程等)没有使用不当使用的字节。 - Eric Postpischil

0

OP: ... 字符数超过10个字节。这怎么可能?
A: 使用 gets() 写入分配的内存之外是未定义行为 - UB。UB 的范围从按您所需工作到崩溃。

真正的问题不在于遗憾地使用了 gets(),而在于 C 语言应该防止内存访问错误的想法。C 并没有阻止它。代码应该防止它。C 不是一个有很多幕后保护的语言。如果写入 ptr[10] 是不好的,请不要这样做。不要调用可能会这样做的函数,比如 gets()。就像生活中的许多方面一样 - 要进行安全计算。

C 给你很多绳子去做各种各样的事情,包括足够的绳子来上吊自己。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接