我开始注意到,有时在我的某些程序中释放内存时,它们会莫名其妙地崩溃。我开始缩小罪魁祸首,并提出了一个例子来说明我难以理解的案例:
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
char *tmp = (char*)malloc(16);
char *tmp2 = (char*)malloc(16);
long address = reinterpret_cast<long>(tmp);
long address2 = reinterpret_cast<long>(tmp2);
cout << "tmp = " << address << "\n";
cout << "tmp2 = " << address2 << "\n";
memset(tmp, 1, 16);
memset(tmp2, 1, 16);
char startBytes[4] = {0};
char endBytes[4] = {0};
memcpy(startBytes, tmp - 4, 4);
memcpy(endBytes, tmp + 16, 4);
cout << "Start: " << static_cast<int>(startBytes[0]) << " " << static_cast<int>(startBytes[1]) << " " << static_cast<int>(startBytes[2]) << " " << static_cast<int>(startBytes[3]) << "\n";
cout << "End: " << static_cast<int>(endBytes[0]) << " " << static_cast<int>(endBytes[1]) << " " << static_cast<int>(endBytes[2]) << " " << static_cast<int>(endBytes[3]) << "\n";
cout << "---------------\n";
free(tmp);
memcpy(startBytes, tmp - 4, 4);
memcpy(endBytes, tmp + 16, 4);
cout << "Start: " << static_cast<int>(startBytes[0]) << " " << static_cast<int>(startBytes[1]) << " " << static_cast<int>(startBytes[2]) << " " << static_cast<int>(startBytes[3]) << "\n";
cout << "End: " << static_cast<int>(endBytes[0]) << " " << static_cast<int>(endBytes[1]) << " " << static_cast<int>(endBytes[2]) << " " << static_cast<int>(endBytes[3]) << "\n";
free(tmp2);
return 0;
}
以下是我看到的输出:
tmp = 8795380
tmp2 = 8795400
Start: 16 0 0 0
End: 16 0 0 0
---------------
Start: 17 0 0 0
End: 18 0 0 0
我正在使用Borland的免费编译器。我知道我正在查看的头字节是特定于实现的,像“reinterpret_cast”这样的东西是不好的做法。我只想找到答案的问题是:为什么“结束”的第一个字节从16变为18?
被认为是“结束”的4个字节是tmp之后的16个字节,它们是tmp2之前的4个字节。它们是tmp2的标题 - 为什么在tmp上调用free()会影响内存中的这个位置?
我尝试了使用new []和delete []来创建/删除tmp和tmp2的相同示例,并且会出现相同的结果。
非常感谢任何有关理解记忆中这个特定地方受影响的信息或帮助。
答案 0 :(得分:5)
您必须询问libc
实施更改原因。无论如何,为什么重要?这是libc尚未分配给您的内存区域,可能用于维护自己的数据结构或一致性检查,或者可能根本不使用。
答案 1 :(得分:2)
基本上你在看你没有分配的记忆。你不能对你所请求的内存(即你分配的16个字节)发生的事情做出任何假设。没有任何异常情况发生。
运行时和编译器可以随意执行任何操作,因此您不应在程序中使用它们。运行时可能会更改这些字节的值以跟踪其内部状态。 释放内存不太可能导致程序崩溃。另一方面,访问你已经取消分配的内存就像你的样本一样,这可能是一个很大的编程错误。
避免这种情况的一个好方法是将任何指针设置为NULL。这样做会在访问释放的变量时强制程序崩溃。
答案 2 :(得分:0)
从堆中删除已分配元素的行为可能会修改其他堆节点,或者实现保留一个或多个字节的头,以用作先前分配的保护字节。
答案 3 :(得分:0)
内存管理器必须记住例如用malloc
分配的内存块的大小。有不同的方法,但最简单的方法是分配比调用中请求的大小多4个字节,并在指针返回给调用者之前存储大小值。
然后free
的实现可以从传递的指针中减去4个字节,以获得指向存储大小的位置的指针,然后可以将块(例如)链接到可用的可重用块列表size(可能再次使用那4个字节来存储到下一个块的链接)。
您不应该更改甚至查看已分配区域之前/之后的字节数。访问,甚至只是为了读取你没有分配的内存的结果是Undefined Behavior(是的,你真的可以让程序真正崩溃或者因为读取未分配的内存而疯狂行为)。