1

I have just concluded a happy 4.5 hours of debugging a nasty leak in my system.

It turns out I was doing this:

params = allocate(sizeof(Something*) * num_params); 

which in turns essentially calls malloc with the first argument passed in. When num_params is 0, it would call malloc(0).

Running this in a loop, the program would very quickly take up most of the memory. I fixed it by first checking if num_params == 0, and if so avoiding the call to allocate.

I know that the Standard dictates malloc(0) is implementation-defined. So, how is this implemented in the Windows 7 C runtime library, and why does it cause a leak?

Edit: Clarifying the question - why does malloc(0) on Windows allocate memory, and what is the logic that dictates how much memory will be allocated?

Aviv Cohn
  • 15,543
  • 25
  • 68
  • 131
  • 4
    A memory leak is not related with malloc( 0 ). It can occur when you do not free the allocated memory allocated with malloc( 0 ). – Vlad from Moscow May 18 '20 at 18:06
  • @VladfromMoscow I understand. Later on in the code I checked `if (num_params > 0) free_things();`. Assuming `malloc(0)` doesn't allocate anything. I assume the question would be why and how does `malloc(0)` allocate memory from the heap. – Aviv Cohn May 18 '20 at 18:08
  • My assumption is that you don't free the memory because you think none was allocated (maybe you are storing and checking the size against zero). `malloc(0)` may still allocate memory that you have to free: ["Typically, the pointer refers to a zero-length block of memory consisting entirely of control structures."](https://wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations) – CherryDT May 18 '20 at 18:08
  • Note that it may very well be zero though. You may have allocated memory worth 0 bytes data + X bytes memory manager metadata. (For example, the metadata may contain the information what the data size is... zero in this case ^^) – CherryDT May 18 '20 at 18:10
  • 2
    When you allocate memory, there's a certain amount of overhead involved. So even allocating 0-bytes takes up memory. – ikegami May 18 '20 at 18:11
  • Can you provide a minimal reproducible example? It would be nice to be able to experiment with this - and to ensure that the error isn't caused by some other, unrelated bug. – templatetypedef May 18 '20 at 18:12
  • 1
    In short, whenever you call `malloc` you should *always* pass the returned pointer to `free`. Note that `free(NULL)` is perfectly valid, so you don't have to check for that. – Some programmer dude May 18 '20 at 18:13
  • But you do have to be careful about `realloc(0)`! See [this](https://wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations) too – CherryDT May 18 '20 at 18:14
  • 1
    OP says "Assuming malloc(0) doesn't allocate anything." It is quite clear in the [man page](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/malloc?view=vs-2019). *If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item.* – Weather Vane May 18 '20 at 18:17

4 Answers4

3

malloc(0) returns a new valid address because that's the option it chose among those the C standard permits.

7.22.3 Memory management functions (emphasis mine)

1 The order and contiguity of storage allocated by successive calls to the aligned_alloc, calloc, malloc, and realloc functions is unspecified. The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated). The lifetime of an allocated object extends from the allocation until the deallocation. Each such allocation shall yield a pointer to an object disjoint from any other object. The pointer returned points to the start (lowest byte address) of the allocated space. If the space cannot be allocated, a null pointer is returned. If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

The implementation on windows chooses the second behavior. And so it must do some allocation to ensure the requirement appearing immediately before. Each valid pointer returned by an allocation function must be disjoint from any other pointer returned by an allocation function.


Relevant reading:

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    What's the motivation to implement the second behavior, is beyond me. – Tony Tannous May 18 '20 at 18:26
  • 1
    @TonyTannous - ¯\\_(ツ)_/¯ But it might make for a good question if none exist already – StoryTeller - Unslander Monica May 18 '20 at 18:28
  • @TonyTannous, that `malloc()` does not spurriously fail seems like a reasonable motivation for choosing the second option. That is also the choice made by glibc. – John Bollinger May 18 '20 at 18:40
  • I don't disagree with this answer, but I'm uncertain whether it constitutes a complete response to the question. The question seems to be saying that `malloc(0)` allocates *unreasonably large amounts* of memory for them, and it has been edited to ask specifically about the size of the allocation. – John Bollinger May 18 '20 at 18:42
  • @john Bollinger interesting. I went to the documentation of malloc.c in glibc. `Minimum allocated size: 4-byte ptrs: 16 bytes (including 4 overhead) 8-byte ptrs: 24/32 bytes (including, 4/8 overhead) ` and `Even a request for zero bytes (i.e., malloc(0)) returns a pointer to something of the minimum allocatable size` – Tony Tannous May 18 '20 at 20:23
  • Ok, @TonyTannous. That verifies my assertion that Glibc's `malloc` handles an argument of 0 by allocating rather than failing. But you could have just gone to the manual for that, and the details of how much space Glibc's `malloc()` allocates are not relevant to the OP's situation. – John Bollinger May 18 '20 at 20:29
  • @TonyTannous "What's the motivation to implement the second behavior, is beyond me." A possible answer: [the memory block can be modified later by realloc](https://learn.microsoft.com/en-us/cpp/c-language/allocating-zero-memory). –  Jan 30 '23 at 20:59
2

Some systems simply return NULL without allocating anything. But it's perfectly legitimate for malloc to allocate a 0 byte block of memory and return a pointer to that memory. This block must be freed like any other allocated by malloc.

When you allocate memory, there's a certain amount of overhead involved. So even allocating 0 bytes takes up memory.

Also, systems may over-allocate or have have alignment restrictions that may render some memory unusable after each allocation. This may or may not happen here.


In the comments, you mentioned you are doing something along the lines of the following:

if (num_params > 0) {
    ...free elements of params...
    free(params);
}

Instead, you should have been doing the following:

if (params) {
    ...free elements of params...
    free(params);
}
ikegami
  • 367,544
  • 15
  • 269
  • 518
0

From the man page:

If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item

And, with the valgrind check for the small program:

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

int main()
{
    int *p = malloc(0);

    if(!p) {
        printf("%p\n", p);
    }
    //free(p);
    return 0;
}

We have the log:

HEAP SUMMARY:
==11874==     in use at exit: 0 bytes in 1 blocks
==11874==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==11874== 
==11874== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==11874==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

But with free function:

HEAP SUMMARY:
==11887==     in use at exit: 0 bytes in 0 blocks
==11887==   total heap usage: 1 allocs, 1 frees, 0 bytes allocated
==11887== 
==11887== All heap blocks were freed -- no leaks are possible

Hitokiri
  • 3,607
  • 1
  • 9
  • 29
  • That is Linux, not Windows. Please see my [comment](https://stackoverflow.com/questions/61876053/why-does-malloc0-cause-a-major-memory-leak-on-windows#comment109441968_61876053). – Weather Vane May 18 '20 at 18:25
  • sorry, about that. I add the link for description of malloc for window – Hitokiri May 18 '20 at 18:31
0

As others have quoted, malloc(0) is implementation defined, a null pointer is returned or the behaviour is as if the size were some nonzero value. Clearly it's the second option and quoting Microsoft's documentation for malloc

If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item.

Doesn't specifiy the overhead.


To get a sense we can look at glibc implementation for malloc.c

Even a request for zero bytes (i.e., malloc(0)) returns a pointer to something of the minimum allocatable size


Minimum allocated size:
4-byte ptrs: 16 bytes (including 4 overhead)
8-byte ptrs: 24/32 bytes (including, 4/8 overhead)

Tony Tannous
  • 14,154
  • 10
  • 50
  • 86