你的意思是“为什么会这样”,
允许是因为C直接访问内存;这是它的力量的一部分。在让您执行之前,很少会检查您正在尝试执行的操作。这就是为什么你需要小心。
为什么它不以崩溃“惩罚”,而不是立即,也许永远不会?因为并不总是禁止在该区域写入(内存保护是面向页面的)。假设当您分配一个内存区域时,它被划分为 1000 个字节的页面。那么如果你分配 50 个字节,底层硬件将解锁 1000 个字节。它无法解锁较小的区域。因此,您“可以”写入所有这 1000 个字节而不会导致保护错误。
现在内存管理器必须跟踪数据在哪里,所以它有自己的结构,而且它也经常“分页”内存。因此,当您请求 50 个字节时,软件内存管理器实际上可能会分配 256 个。然后,如果您将这 50 个字节重新分配到 100,您将看到指针没有改变。如果你 realloc() 那些到 257 字节,指针 确实 改变 - 内存管理器不能将该块扩大到 257 字节,所以它将它标记为空闲,并从硬件在其他地方分配一个 512 块。如果你随后 alloc() 42 字节,你可能会发现它的指针与之前指向你的 100 字节缓冲区的地址相同。
有时,一些调试库不仅会分配一个区域,还会用金丝雀“保护”它。您要求 50 个字节,库分配 66 个字节并在这 66 个字节内返回一个指针 8 个字节。它用已知值填充前 8 个字节和后 8 个字节。它会不时检查该值是否仍然存在;如果不是,则会引发软崩溃以警告您溢出(或下溢)缓冲区。
在您的示例中,没有此类保护,您可以在分配的额外区域中写入。但很可能该区域稍后会被使用并被覆盖:也许,如果你这样做
foo = malloc(20);
strcpy(foo, "string ... 30 bytes long");
bar = malloc(20); // ^20th byte
strcpy(bar, "hello world");
然后打印 foo,你会得到“string ... 3hello world”。或“字符串 ...[GARBAGE]hello world”。通过在 foo 之后写入 bar,您覆盖了存储数据的区域。
再说一次,如果你从来没有在 bar 中写过任何东西,那么程序可能会工作并且不会抱怨。
然后你在不同的平台或不同的库上编译,一个运行了多年的程序突然崩溃了。欢迎来到未定义行为的世界。
有几个库和工具可用于解决此类问题 - 一个非常好的工具是 valgrind。
“删除”字符串和/或释放其内存
// I initialize the pointer to NULL. If I just declared the pointer,
// its initial value might be anything. This way, I reduce the random
// element in my program. Makes no difference... except that one time
// when it does, and will save your bacon.
char *pwd = NULL;
// Every malloc and realloc MUST check that it did not return NULL,
// meaning an error occurred. Even for small memory blocks.
if (NULL === (pwd = malloc(200))) {
// Handle out of memory error
}
strcpy(pwd, "Squeamish Ossifrage"):
// ... do something with pwd
// ...we're done. If we just freed this area, its contents would remain
// available *and* the pointer would still point to it. so this works:
/*
free(pwd);
printf("The secret word is %s\n", pwd);
...but might explode at any moment.
*/
// pwd contains sensitive data, so we first zero it, and this requires
// remembering the actual size of the allocated block. Here, 200.
memset(pwd, 0, 200);
// Now we free the area pointed to by the pointer. Then we also
// erase the pointer.
free(pwd); pwd = NULL;
通过在同一行写free和NULL,我可以运行
grep 'free\\s*(' | grep -v "NULL;"
并找到所有 free() 没有 NULL 赋值的行,并将这些行标记为可能需要改进。
现在,如果我在释放 pwd 后使用它,它将永远无法工作,这会进一步消除执行中的随机性。