我想知道malloc和free是怎么工作的。

int main() {
    unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
    memset(p,0,4);
    strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
    cout << p;
    free(p); // Obvious Crash, but I need how it works and why crash.
    cout << p;
    return 0;
}

如果答案是在记忆层面上的深度,如果可能的话,我会非常感激。


当前回答

malloc/free的一个实现如下所示:

通过sbrk() (Unix调用)从操作系统获取一块内存。 在该内存块周围创建一个页眉和页脚,并提供一些信息,如大小、权限以及下一个和上一个块的位置。 当传入对malloc的调用时,引用一个指向适当大小的块的列表。 然后返回这个块,页眉和页脚也相应地更新。

其他回答

理论上,malloc为这个应用程序从操作系统获取内存。然而,由于您可能只需要4个字节,而操作系统需要在页面上工作(通常是4k), malloc所做的要比这多一点。它取一个页面,并把它自己的信息放在那里,这样它就可以跟踪你从该页中分配和释放了什么。

例如,当分配4个字节时,malloc会提供一个指向4个字节的指针。您可能没有意识到的是,在4个字节之前的8-12个字节的内存被malloc用来构成您已分配的所有内存的链。当你调用free时,它会取你的指针,备份到它的数据所在的位置,并对其进行操作。

当你释放内存时,malloc将内存块从链上取下…并且可能会也可能不会将这些内存返回给操作系统。如果它这样做,那么访问内存可能会失败,因为操作系统将拿走你访问该位置的权限。如果malloc保留内存(因为它在该页中分配了其他内容,或者用于某些优化),则访问将正常工作。这仍然是错误的,但可能会起作用。

免责声明:我所描述的是malloc的一种常见实现,但绝不是唯一可能的实现。

同样重要的是要意识到,简单地使用brk和sbrk移动程序断点指针实际上并不分配内存,它只是设置了地址空间。例如,在Linux上,当访问该地址范围时,内存将由实际的物理页“备份”,这将导致页错误,并最终导致内核调用页分配器以获得备份页。

malloc/free的一个实现如下所示:

通过sbrk() (Unix调用)从操作系统获取一块内存。 在该内存块周围创建一个页眉和页脚,并提供一些信息,如大小、权限以及下一个和上一个块的位置。 当传入对malloc的调用时,引用一个指向适当大小的块的列表。 然后返回这个块,页眉和页脚也相应地更新。

好的,关于malloc的一些答案已经发布了。

更有趣的部分是free是如何工作的(在这个方向上,malloc也可以更好地理解)。

In many malloc/free implementations, free does normally not return the memory to the operating system (or at least only in rare cases). The reason is that you will get gaps in your heap and thus it can happen, that you just finish off your 2 or 4 GB of virtual memory with gaps. This should be avoided, since as soon as the virtual memory is finished, you will be in really big trouble. The other reason is, that the OS can only handle memory chunks that are of a specific size and alignment. To be specific: Normally the OS can only handle blocks that the virtual memory manager can handle (most often multiples of 512 bytes e.g. 4KB).

所以返回40字节到操作系统将不能工作。那么,免费有什么作用呢?

Free会把内存块放在它自己的空闲块列表中。通常情况下,它还尝试将地址空间中的相邻块融合在一起。空闲块列表只是一个循环的内存块列表,在开始的时候有一些管理数据。这也是为什么使用标准malloc/free管理非常小的内存元素效率不高的原因。每个内存块都需要额外的数据,越小的内存块就会产生越多的碎片。

当需要新的内存块时,malloc首先查看的还是空闲列表。在它从操作系统调用新内存之前,它会被扫描。当发现一个块比所需的内存大时,它被分为两部分。一个返回给调用者,另一个返回到空闲列表中。

对于这个标准行为有许多不同的优化(例如对于小块内存)。但是,由于malloc和free必须是通用的,所以当不可用替代方案时,标准行为总是一个退路。在处理空闲列表方面也有优化——例如将块存储在按大小排序的列表中。但是所有的优化也有其局限性。

为什么你的代码崩溃:

原因是,通过将9个字符(不要忘记后面的空字节)写入大小为4个字符的区域,您可能会覆盖存储在您的数据块“后面”的另一个内存块的管理数据(因为该数据通常存储在内存块的“前面”)。当free试图将您的块放入空闲列表时,它会接触到这个管理数据,因此会碰到一个被覆盖的指针。这会使系统崩溃。

This is a rather graceful behaviour. I have also seen situations where a runaway pointer somewhere has overwritten data in the memory-free-list and the system did not immediately crash but some subroutines later. Even in a system of medium complexity such problems can be really, really hard to debug! In the one case I was involved, it took us (a larger group of developers) several days to find the reason of the crash -- since it was in a totally different location than the one indicated by the memory dump. It is like a time-bomb. You know, your next "free" or "malloc" will crash, but you don't know why!

这些是一些最糟糕的C/ c++问题,也是指针问题如此严重的原因之一。

正如aluser在这个论坛上说的:

Your process has a region of memory, from address x to address y, called the heap. All your malloc'd data lives in this area. malloc() keeps some data structure, let's say a list, of all the free chunks of space in the heap. When you call malloc, it looks through the list for a chunk that's big enough for you, returns a pointer to it, and records the fact that it's not free any more as well as how big it is. When you call free() with the same pointer, free() looks up how big that chunk is and adds it back into the list of free chunks(). If you call malloc() and it can't find any large enough chunk in the heap, it uses the brk() syscall to grow the heap, i.e. increase address y and cause all the addresses between the old y and the new y to be valid memory. brk() must be a syscall; there is no way to do the same thing entirely from userspace.

Malloc()依赖于系统/编译器,所以很难给出一个具体的答案。基本上,它会跟踪它所分配的内存,这取决于它是如何做的,所以你对free的调用可能失败或成功。

malloc()和free()并不是在每个O/S上都以相同的方式工作。