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

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


当前回答

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

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

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

如果需要,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.

其他回答

How malloc() and free() works depends on the runtime library used. Generally, malloc() allocates a heap (a block of memory) from the operating system. Each request to malloc() then allocates a small chunk of this memory be returning a pointer to the caller. The memory allocation routines will have to store some extra information about the block of memory allocated to be able to keep track of used and free memory on the heap. This information is often stored in a few bytes just before the pointer returned by malloc() and it can be a linked list of memory blocks.

通过写入超过malloc()分配的内存块,您很可能会破坏下一个块的一些簿记信息,这些信息可能是剩余的未使用的内存块。

在向缓冲区复制太多字符时,程序也可能崩溃。如果额外的字符位于堆之外,当您试图写入不存在的内存时,可能会遇到访问冲突。

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

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

你的程序崩溃是因为它使用了不属于你的内存。它可能被其他人使用,也可能没有——如果你幸运的话,你崩溃了,如果没有,问题可能会隐藏很长一段时间,然后回来咬你一口。

就malloc/free实现而言——整本书都致力于这个主题。基本上,分配器会从操作系统中获得更大的内存块,并为你管理它们。分配器必须解决的一些问题是:

How to get new memory How to store it - ( list or other structure, multiple lists for memory chunks of different size, and so on ) What to do if the user requests more memory than currently available ( request more memory from OS, join some of the existing blocks, how to join them exactly, ... ) What to do when the user frees memory Debug allocators may give you bigger chunk that you requested and fill it some byte pattern, when you free the memory the allocator can check if wrote outside of the block ( which is probably happening in your case) ...

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

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)之类的东西,以了解一个相当快的分配器是如何工作的。

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

正如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上都以相同的方式工作。