内存保护具有页面粒度,并且需要内核交互
你的示例代码本质上是在问为什么示例程序没有陷阱,答案是内存保护是一个内核特性,只应用于整个页面,而内存分配器是一个库特性,它管理。没有强制执行…任意大小的块,通常比页面小得多。
内存只能以页为单位从程序中删除,即使这样也不太可能被观察到。
如果需要,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.