有人曾问我,在调用
p = malloc(strlen(string));该调用
strcpy(p, string);后,它应该成功还是失败。他们怀疑strcpy调用应该失败,因为终止符的空间\0没有分配。他们怀疑指针p不会指向字符串的正确副本,当它指向了,或者看起来指向了,他们感到惊讶。他们具体想知道的是 (a) 稍后的free()对p是否会失败,(b)strcpy()调用是否会失败,(c) 尝试打印字符串p是否会成功,或者 (d) 是否会出现错误。这是我回复的一个修改版本。]
精确地问这里会发生什么是一个棘手的问题。从逻辑上讲,这个问题大致等同于
你来到一个繁忙的十字路口。红灯亮着,并且牌子上写着“不要走”。你过马路。会发生什么?
(a) 警察给你开罚单
(b) 汽车撞到你,你住进了医院
(c) 汽车紧急刹车,险些撞到你,司机对你的智商和祖先进行了各种粗鲁的评论
(d) 你安全地到达了另一边
你正确地观察到代码未能为 '\0' 分配足够的空间。它之所以在您尝试时似乎有效,是因为有时,我过马路闯红灯的比喻中的答案 (d) 适用。
C 是一种相对底层的语言。它被设计用来高效地处理内存中的单个字节和字;它只是似乎为您提供了对字符串等更高级概念的访问。
您上面提供的代码片段询问malloc分配 n 字节内存。malloc返回一个指向某块内存的指针,代码接着将 n+1 字节的数据复制到其中。显然这是错的——嗯,这是你我都知道的,但对malloc或strcpy. strcpy来说并非如此。它不知道可以复制多少字节的内存;它只接收源字符串的指针和目标字符串的指针。通常,指针被实现为直接的内存地址;它们不会携带有关它们指向的内存区域大小的任何额外信息。通常,由您,程序员,来确保指针指向的内存足以支持您对其执行的操作。如果程序员失败了,很可能会发生糟糕的事情,但我们通常无法精确预测糟糕的事情是什么,或者它们何时会发生。我们所能做的就是尽量避免编写不正确的代码,并记住,当我们有一个程序行为异常,或者似乎要覆盖它不应该覆盖的内存,或者因“段错误”或“总线错误”或“通用保护故障”而崩溃时,在调试时需要回头双重检查的事情之一就是我们的指针使用是否正确。
当您将比请求的更多数据复制到一块malloc已分配的数据块时,可能会发生几种情况,具体取决于您意外写入块末尾的距离,以及malloc的实现方式,以及您的程序接下来尝试做什么。通常,如果您写入的数据略微超出(例如,因为忘记为终止符分配空间'\0'字符串,这当然是很常见的错误),您会覆盖一些malloc的内部簿记信息。通常,如果发生这种情况,下一次调用malloc时,代码才不会注意到malloc或free(这可能离实际错误有一段距离,这也是这些错误顽固且难以追踪的原因之一)。有时,malloc会分配比您要求的稍多一些的空间(出于它自己的原因,例如为了使分配的内存块更好地对齐),这意味着您可以(似乎)毫发无伤地溢出该块。
但我必须补充一点,上一段中的所有内容都是“也许”。您当然不应该将更多数据复制到一块malloc已分配的数据块中,超过该块malloc的容量,如果您这样做了,所有赌注都将落空。程序几乎可以做任何事情,包括以各种方式崩溃,或者给出明显错误的结果,或者给出您甚至可能没有注意到的微妙错误结果。但是——而且这实际上相当不幸——不正确程序可能做的“几乎任何事情”也包括“恰好做你期望的事情”(也就是说,根本看起来没有不正确的行为),从而让您错误地认为代码是“正确的”——因为它“有效”——尽管它不正确,今天有效只是偶然,而且很可能会在明天停止工作。