Search code examples
cmemory-managementmallocfreecpu-architecture

C: malloc(), free() and then again malloc() does work same always?


I have tried to run this code in some different machines with different processor and main memory size.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    // your code goes here

    int *a, i;

    a = (int *)malloc(10*(sizeof(int)));

    for(i = 0; i < 10; i++)
    {
        a[i] = i*i;
    }

    free(a); 

    a = (int *)malloc(10*(sizeof(int)));

    for(i = 0; i < 10; i++)
    {
        printf("%d\n",a[i]);
    }

    free(a);

    return 0;
}

However, in all machines it generates the same output:

0
1
4
9
16
25
36
49
64
81

My question is: should the variable 'a' always allocate use the same memory location for the execution?

In theory, probably it is not mandatory. If my understanding is right: every malloc of 'a' can allocate a different base address.

However, what does actually happen in practice by modern computing machines?


Solution

  • It happens in practice because re-using a just-freed chunk of memory is usually the best choice. It's probably still hot in cache, and it means malloc's free-list can be shortened (instead of leaving that block on the free list and getting a new block from the OS).

    It also means malloc probably doesn't need any system calls to satisfy this request. Small free() calls are typically not returned to the OS right away (because usually they can't be, because they're just part of a page). So typical malloc implementations put them on a free-list.

    So this little experiment has told you something about the internal implementation of malloc on the particular C implementation you tried it on. As I think you realize from the question phrasing, this is undefined behaviour and should never be relied upon.

    It's not really specific to CPU architecture. AFAIK, GNU libc's malloc uses the same algorithms on every architecture.


    Even on a C implementation that does work this way, there are many ways for this to break: a signal handler could run between the free and the second malloc, and take the block. In multithreaded code, another thread could take that block between the free and the malloc. (Although this is less likely: each thread would usually use its own small pool).


    A large buffer usually would be handed back to the OS inside the free() call, so the next malloc() would have to get fresh memory from the OS again. See M_MMAP_THRESHOLD in mallopt(3) for an explanation of that tunable parameter in glibc's malloc. M_PERTURB provides similar functionality to what Adrian describes for MSVC++ debug builds: breaking code that has use-after-free bugs, and breaking code that depends on malloced memory being zeroed or something (use calloc to efficiently get zeroed memory).