[有人问我,调用
p = malloc(1);后,为什么似乎可以复制比 1 字节长得多的字符串到p中。这是我的回答。]
这里有几个不同的答案,取决于你到底想问什么。
听起来你已经意识到了程序可能无法工作的的原因。你说得对,它可能无法工作,不能期望它能工作。如果基于程序“碰巧”可以工作这一事实,你倾向于认为那些其他原因都是错误的,请不要这样做!程序绝对是错误的。但软件测试有一个不幸的悖论:如果一个程序不能工作,它肯定有某种错误,但如果另一方面它似乎能工作,我们却*不能*由此得出它没有错误的结论!
回答这个问题的另一种方式是:凌晨 3 点,你走在一条荒凉的街道上。你来到一个十字路口。信号灯是红色的,并且标志上写着“不要走”。你决定还是过马路,尽管这是非法的,也是错误的。当你过马路时,你没有被汽车撞到,也没有警察逮捕你——你安然无恙地到达了对面。为什么?
或者,正如 Roger Miller 所写:“有人告诉我,在篮球比赛中你不能持球跑。我拿了个篮球试了试,结果非常好。他显然不懂篮球。”
如果你想从低级别了解程序是如何在存在严重错误的情况下工作的,解释其实并不比上面提到的交通灯的类比离谱多少。你请求malloc提供一个指向 1 字节内存的指针。malloc给你这样一个指针,但你的计算机肯定有超过 1 字节的可用内存,所以你得到的指针malloc指向你程序可用内存的中间某个位置。你*应该*只写入你请求的 1 字节内存malloc“给你”的,但你请求的 1 字节之外还有内存,而且没有任何机制,也没有警察会立即注意到并抱怨你写入的量超过了你应有的量。
(也许更好的类比是这样:你的父母经营一家杂货店。有一天你向你母亲要一美元。你母亲信任你,所以她说你可以去店前面的收银机里拿一美元。你打开收银机的抽屉,里面有 500 美元。如果你拿了比你被委托拿的一美元多得多的钱,会发生什么?你最终可能会被抓住,但可能不会马上被抓住。)
如果你想说服自己,向 malloc 分配的区域写入比应有的内容多是错误的,如果你想看到这样做通常会产生的错误类型,你需要一个程序,在它犯错误后,能够做更多的事情,这样你和/或计算机和/或 malloc 库才有机会注意到事情出错。例如,你可以分配两个指针,并注意到当你溢出其中一个指针时,第二个字符串(可能)会受到影响
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *p1 = malloc(1); char *p2 = malloc(20); strcpy(p2, "Hello, world!"); printf("p2 = \"%s\"\n", p2); strcpy(p1, "This is more than I should write to one byte"); printf("p1 = \"%s\"\n", p1); printf("p2 = \"%s\"\n", p2); return 0; }
或者,你可以复制大量字符
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *p1 = malloc(1); char *p2 = malloc(1); memcpy(p1, p2, 1000000); return 0; }
我在我的计算机上测试了这两个程序,它们都如预期般失败(尽管我不能保证它们在你那里也会失败,或者以同样的方式失败)。
另一种注意到 malloc 分配内存出错的方法是*始终*检查来自malloc的返回值。不检查返回值是很诱人的,尤其是当你分配少量内存时,事实上我在上面的两个程序中也屈服于了这种诱惑。毕竟,正如我们刚才指出的,我们的计算机肯定有不止一字节的内存,所以调用malloc(1)“永远”不会失败,对吧?嗯,不,实际上它会失败。malloc通常也会在发现你写入了之前给你的其中一个指针的内容超过了应有量时返回 NULL。所以你*应该*始终检查 malloc 的返回值。(而且,反正,总有一天你可能会真的耗尽内存。)所以这里是另一个测试程序
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *p1, *p2; p1 = malloc(1); if(p1 == NULL) { fprintf(stderr, "malloc failed\n"); exit(EXIT_FAILURE); } strcpy(p1, "This is more than I should write to one byte"); printf("p1 = \"%s\"\n", p1); p2 = malloc(1); if(p2 == NULL) { fprintf(stderr, "second malloc failed\n"); exit(EXIT_FAILURE); } printf("p1 = \"%s\"\n", p1); *p2 = 'X'; printf("p1 = \"%s\"\n", p1); return EXIT_SUCCESS; }
我曾预计第二次调用malloc会失败,因为我复制了比应有的文本更多的内容到p1。在我的电脑上,出于某种原因,第二次调用 malloc 实际上成功了,但很有可能在你的电脑上它会失败。