[c++] How do malloc() and free() work?

I want to know how malloc and free work.

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;
}

I would be really grateful if the answer is in depth at memory level, if it's possible.

This question is related to c++ c memory-management malloc free

The answer is


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.

By writing past the block of memory allocated by malloc() you will most likely destroy some of the book-keeping information of the next block which may be the remaining unused block of memory.

One place where you program may also crash is when copying too many characters into the buffer. If the extra characters are located outside the heap you may get an access violation as you are trying to write to non-existing memory.


It's also important to realize that simply moving the program break pointer around with brk and sbrk doesn't actually allocate the memory, it just sets up the address space. On Linux, for example, the memory will be "backed" by actual physical pages when that address range is accessed, which will result in a page fault, and will eventually lead to the kernel calling into the page allocator to get a backing page.


This has nothing specifically to do with malloc and free. Your program exhibits undefined behaviour after you copy the string - it could crash at that point or at any point afterwards. This would be true even if you never used malloc and free, and allocated the char array on the stack or statically.


One implementation of malloc/free does the following:

  1. Get a block of memory from the OS through sbrk() (Unix call).
  2. Create a header and a footer around that block of memory with some information such as size, permissions, and where the next and previous block are.
  3. When a call to malloc comes in, a list is referenced which points to blocks of the appropriate size.
  4. This block is then returned and headers and footers are updated accordingly.

Memory protection has page-granularity and would require kernel interaction

Your example code essentially asks why the example program doesn't trap, and the answer is that memory protection is a kernel feature and applies only to entire pages, whereas the memory allocator is a library feature and it manages .. without enforcement .. arbitrary sized blocks which are often much smaller than pages.

Memory can only be removed from your program in units of pages, and even that is unlikely to be observed.

calloc(3) and malloc(3) do interact with the kernel to get memory, if necessary. But most implementations of free(3) do not return memory to the kernel1, they just add it to a free list that calloc() and malloc() will consult later in order to reuse the released blocks.

Even if a free() wanted to return memory to the system, it would need at least one contiguous memory page in order to get the kernel to actually protect the region, so releasing a small block would only lead to a protection change if it was the last small block in a page.

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.

Theory of Operation

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.


In theory, malloc gets memory from the operating system for this application. However, since you may only want 4 bytes, and the OS needs to work in pages (often 4k), malloc does a little more than that. It takes a page, and puts it's own information in there so it can keep track of what you have allocated and freed from that page.

When you allocate 4 bytes, for instance, malloc gives you a pointer to 4 bytes. What you may not realize is that the memory 8-12 bytes before your 4 bytes is being used by malloc to make a chain of all the memory you have allocated. When you call free, it takes your pointer, backs up to where it's data is, and operates on that.

When you free memory, malloc takes that memory block off the chain... and may or may not return that memory to the operating system. If it does, than accessing that memory will probably fail, as the OS will take away your permissions to access that location. If malloc keeps the memory ( because it has other things allocated in that page, or for some optimization ), then the access will happen to work. It's still wrong, but it might work.

DISCLAIMER: What I described is a common implementation of malloc, but by no means the only possible one.


Your program crashes because it used memory that does not belong to you. It may be used by someone else or not - if you are lucky you crash, if not the problem may stay hidden for a long time and come back and bite you later.

As far as malloc/free implementation goes - entire books are devoted to the topic. Basically the allocator would get bigger chunks of memory from the OS and manage them for you. Some of the problems an allocator must address are:

  • 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) ...

Well it depends on the memory allocator implementation and the OS.

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 will then remove the header and add it back to the free memory list. If it forms a larger block with the surrounding free blocks these will be added together to give a larger block. If a whole page is now free the allocator will, most likely, return the page to the OS.

It is not a simple problem. The OS allocator portion is completely out of your control. I recommend you read through something like Doug Lea's Malloc (DLMalloc) to get an understanding of how a fairly fast allocator will work.

Edit: Your crash will be caused by the fact that by writing larger than the allocation you have overwritten the next memory header. This way when it frees it gets very confused as to what exactly it is free'ing and how to merge into the following block. This may not always cause a crash straight away on the free. It may cause a crash later on. In general avoid memory overwrites!


As aluser says in this forum thread:

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() is system/compiler dependent so it's hard to give a specific answer. Basically however it does keep track of what memory it's allocated and depending on how it does so your calls to free could fail or succeed.

malloc() and free() don't work the same way on every O/S.


malloc and free are implementation dependent. A typical implementation involves partitioning available memory into a "free list" - a linked list of available memory blocks. Many implementations artificially divide it into small vs large objects. Free blocks start with information about how big the memory block is and where the next one is, etc.

When you malloc, a block is pulled from the free list. When you free, the block is put back in the free list. Chances are, when you overwrite the end of your pointer, you are writing on the header of a block in the free list. When you free your memory, free() tries to look at the next block and probably ends up hitting a pointer that causes a bus error.


It's hard to say because the actual behaviour is different between different compilers/runtimes. Even debug/release builds have different behaviour. Debug builds of VS2005 will insert markers between allocations to detect memory corruption, so instead of a crash, it will assert in free().


Your strcpy line attempts to store 9 bytes, not 8, because of the NUL terminator. It invokes undefined behaviour.

The call to free may or may not crash. The memory "after" the 4 bytes of your allocation might be used for something else by your C or C++ implementation. If it is used for something else, then scribbling all over it will cause that "something else" to go wrong, but if it isn't used for anything else, then you could happen to get away with it. "Getting away with it" might sound good, but is actually bad, since it means your code will appear to run OK, but on a future run you might not get away with it.

With a debugging-style memory allocator, you might find that a special guard value has been written there, and that free checks for that value and panics if it doesn't find it.

Otherwise, you might find that the next 5 bytes includes part of a link node belonging to some other block of memory which hasn't been allocated yet. Freeing your block could well involved adding it to a list of available blocks, and because you've scribbled in the list node, that operation could dereference a pointer with an invalid value, causing a crash.

It all depends on the memory allocator - different implementations use different mechanisms.


Examples related to c++

Method Call Chaining; returning a pointer vs a reference? How can I tell if an algorithm is efficient? Difference between opening a file in binary vs text How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Install Qt on Ubuntu #include errors detected in vscode Cannot open include file: 'stdio.h' - Visual Studio Community 2017 - C++ Error How to fix the error "Windows SDK version 8.1" was not found? Visual Studio 2017 errors on standard headers How do I check if a Key is pressed on C++

Examples related to c

conflicting types for 'outchar' Can't compile C program on a Mac after upgrade to Mojave Program to find largest and second largest number in array Prime numbers between 1 to 100 in C Programming Language In c, in bool, true == 1 and false == 0? How I can print to stderr in C? Visual Studio Code includePath "error: assignment to expression with array type error" when I assign a struct field (C) Compiling an application for use in highly radioactive environments How can you print multiple variables inside a string using printf?

Examples related to memory-management

When to create variables (memory management) How to check if pytorch is using the GPU? How to delete multiple pandas (python) dataframes from memory to save RAM? Is there a way to delete created variables, functions, etc from the memory of the interpreter? C++ error : terminate called after throwing an instance of 'std::bad_alloc' How to delete object? Android Studio - How to increase Allocated Heap Size Implementing IDisposable correctly Calculating Page Table Size Pointer-to-pointer dynamic two-dimensional array

Examples related to malloc

Initializing C dynamic arrays C - freeing structs malloc an array of struct pointers How do I free memory in C? How to dynamically allocate memory space for a string and get that string from user? Incompatible implicit declaration of built-in function ‘malloc’ Dynamically create an array of strings with malloc How is malloc() implemented internally? Why do I get a C malloc assertion failure? What is a Memory Heap?

Examples related to free

C free(): invalid pointer double free or corruption (!prev) error in c program How to track down a "double free or corruption" error How do malloc() and free() work? What REALLY happens when you don't free after malloc?