cclangsanitizerc23

Why clang's sanitizer doesn't report memory leaks for global scope variables?


I have the following code:

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

int main() {
    void* a = malloc(10);
    printf("%p\n", a);
}

When compiled and run:

clang-19 -std=c23 -fsanitize=address -g -Weverything -o test_mem.exe test_mem.c
./test_mem.exe
0x502000000010

=================================================================
==212167==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 10 byte(s) in 1 object(s) allocated from:
    #0 0x55f0442625cf in malloc (/home/xxx/xxx/xxx/test_mem.exe+0xcb5cf) (BuildId: fe83f3b1cd62d6171e5d431d34ee529cce48f18f)
    #1 0x55f0442a2a68 in main /xxx/xxx/xxx/xxx/test_mem.c:8:11
    #2 0x7fa09f96fd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

SUMMARY: AddressSanitizer: 10 byte(s) leaked in 1 allocation(s).

The memory leak is detected. Great!

However, if I change the code to:

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

void* a = nullptr;

int main() {
    a = malloc(10);
    printf("%p\n", a);
}

and compile/run:

clang-19 -std=c23 -fsanitize=address -g -Weverything -o test_mem.exe test_mem.c
... compiler complains about `int* a` not being static, fine...
./test_mem.exe
0x502000000010

No memory leak is detected. Making the int* a static does not change this behavior (the compiler just reports it as a warning).

I am sure there's a "good reason" why the memory leak is not reported when the variable is declared globally, but:

  1. Why is this the case? Is there a specific reason for this behavior? Could it be that when a variable is declared at the global scope, its memory is automatically freed, even when dynamically allocated? If this is the case, does it mean freeing that memory, like doing a free(a) isn't strictly necessary?

  2. Is there a way to configure the sanitizer or compiler to detect such memory leaks for global variables? Some flags, perhaps?


Solution

  • A memory leak occurs when all pointers to a block of allocated memory are lost before the lifetime of that block ends, i.e. before the pointer is passed to free.

    If such a pointer is stored in a global variable, or more accurately a variable at file scope whose lifetime is that of the entire program, then that pointer to allocated memory is never lost for the life of the program, so no memory leak.

    Tools like valgrind (which check behavior at run time) will note something like this, stating that the allocated memory is "still reachable" as opposed to leaked.

    The valgrind output for this code is as follows:

    ==14636== Memcheck, a memory error detector
    ==14636== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==14636== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==14636== Command: ./x1
    ==14636== 
    0x5205040
    ==14636== 
    ==14636== HEAP SUMMARY:
    ==14636==     in use at exit: 10 bytes in 1 blocks
    ==14636==   total heap usage: 1 allocs, 0 frees, 10 bytes allocated
    ==14636== 
    ==14636== LEAK SUMMARY:
    ==14636==    definitely lost: 0 bytes in 0 blocks
    ==14636==    indirectly lost: 0 bytes in 0 blocks
    ==14636==      possibly lost: 0 bytes in 0 blocks
    ==14636==    still reachable: 10 bytes in 1 blocks
    ==14636==         suppressed: 0 bytes in 0 blocks
    ==14636== Rerun with --leak-check=full to see details of leaked memory
    ==14636== 
    ==14636== For lists of detected and suppressed errors, rerun with: -s
    ==14636== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)