我想知道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;
}

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


当前回答

这很难说,因为不同的编译器/运行时之间的实际行为是不同的。即使是调试/发布版本也有不同的行为。VS2005的调试版本将在分配之间插入标记来检测内存损坏,因此它将在free()中断言,而不是崩溃。

其他回答

这取决于内存分配器的实现和操作系统。

Under windows for example a process can ask for a page or more of RAM. The OS then assigns those pages to the process. This is not, however, memory allocated to your application. The CRT memory allocator will mark the memory as a contiguous "available" block. The CRT memory allocator will then run through the list of free blocks and find the smallest possible block that it can use. It will then take as much of that block as it needs and add it to an "allocated" list. Attached to the head of the actual memory allocation will be a header. This header will contain various bit of information (it could, for example, contain the next and previous allocated blocks to form a linked list. It will most probably contain the size of the allocation).

Free将删除头文件并将其添加回空闲内存列表。如果它与周围的自由块形成一个更大的块,这些块将被加在一起,形成一个更大的块。如果整个页面现在是空闲的,分配器很可能会将该页返回给操作系统。

这不是一个简单的问题。操作系统分配器部分完全不受您的控制。我建议您阅读Doug Lea的Malloc (DLMalloc)之类的东西,以了解一个相当快的分配器是如何工作的。

编辑:你的崩溃将由这样一个事实引起,即写大于分配,你已经覆盖了下一个内存头。这样,当它释放时,它会非常困惑,不知道它到底释放了什么,以及如何合并到下面的块中。这可能并不总是会直接导致免费的崩溃。这可能会导致以后的崩溃。一般避免内存覆盖!

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

好的,关于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++问题,也是指针问题如此严重的原因之一。

内存保护具有页面粒度,并且需要内核交互

你的示例代码本质上是在问为什么示例程序没有陷阱,答案是内存保护是一个内核特性,只应用于整个页面,而内存分配器是一个库特性,它管理。没有强制执行…任意大小的块,通常比页面小得多。

内存只能以页为单位从程序中删除,即使这样也不太可能被观察到。

如果需要,Calloc(3)和malloc(3)会与内核交互以获取内存。但是大多数free(3)的实现都不会将内存返回给内核1,它们只是将内存添加到一个空闲列表中,稍后calloc()和malloc()会参考这个列表,以便重用释放的块。

即使free()函数想要将内存返回给系统,它也需要至少一个连续的内存页才能让内核实际保护该区域,因此释放一个小块只会导致保护更改,如果它是页面中的最后一个小块。

So your block is there, sitting on the free list. You can almost always access it and nearby memory just as if it were still allocated. C compiles straight to machine code and without special debugging arrangements there are no sanity checks on loads and stores. Now, if you try and access a free block, the behavior is undefined by the standard in order to not make unreasonable demands on library implementators. If you try and access freed memory or meory outside an allocated block, there are various things that can go wrong:

Sometimes allocators maintain separate blocks of memory, sometimes they use a header they allocate just before or after (a "footer", I guess) your block, but they just might want to use memory within the block for the purpose of keeping the free list linked together. If so, your reading the block is OK, but its contents may change, and writing to the block would be likely to cause the allocator to misbehave or crash. Naturally, your block may be allocated in the future, and then it is likely to be overwritten by your code or a library routine, or with zeroes by calloc(). If the block is reallocated, it may also have its size changed, in which case yet more links or initialization will be written in various places. Obviously you may reference so far out of range that you cross a boundary of one of your program's kernel-known segments, and in this one case you will trap.

操作原理

So, working backwards from your example to the overall theory, malloc(3) gets memory from the kernel when it needs it, and typically in units of pages. These pages are divided or consolidated as the program requires. Malloc and free cooperate to maintain a directory. They coalesce adjacent free blocks when possible in order to be able to provide large blocks. The directory may or may not involve using the memory in freed blocks to form a linked list. (The alternative is a bit more shared-memory and paging-friendly, and it involves allocating memory specifically for the directory.) Malloc and free have little if any ability to enforce access to individual blocks even when special and optional debugging code is compiled into the program.


1. The fact that very few implementations of free() attempt to return memory to the system is not necessarily due to the implementors slacking off. Interacting with the kernel is much slower than simply executing library code, and the benefit would be small. Most programs have a steady-state or increasing memory footprint, so the time spent analyzing the heap looking for returnable memory would be completely wasted. Other reasons include the fact that internal fragmentation makes page-aligned blocks unlikely to exist, and it's likely that returning a block would fragment blocks to either side. Finally, the few programs that do return large amounts of memory are likely to bypass malloc() and simply allocate and free pages anyway.

Malloc和free依赖于实现。典型的实现包括将可用内存划分为“空闲列表”——可用内存块的链表。许多实现人为地将它分为小对象和大对象。空闲块以内存块有多大以及下一个内存块在哪里等信息开始。

当你malloc时,一个块从空闲列表中拉出来。当你释放时,块被放回空闲列表。很有可能,当你重写指针的末尾时,你是在写空闲列表中一个块的头。当您释放内存时,free()尝试查看下一个块,并可能最终击中导致总线错误的指针。