admin管理员组

文章数量:1122846

How realloc works passed 0 size as argument?

from man page: Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc(), or realloc().

Why it needs to be?

compile this with gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 with no options(flags)

this sample

#include <stdlib.h>

int main () {
    int *p = malloc(0);
    p = realloc(p, 0);
    return 0;
}

this is working code, checking memory with valgrind show this:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==185872== Memcheck, a memory error detector
==185872== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==185872== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==185872== Command: ./test
==185872== 
==185872== 
==185872== HEAP SUMMARY:
==185872==     in use at exit: 0 bytes in 0 blocks
==185872==   total heap usage: 1 allocs, 1 frees, 0 bytes allocated
==185872== 
==185872== All heap blocks were freed -- no leaks are possible
==185872== 
==185872== For lists of detected and suppressed errors, rerun with: -s
==185872== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 

but then compile this(added flag -g):

#include <stdlib.h>

int main () {
    int *p = NULL;
    p = realloc(p, 0);
    return 0;
}

valgrind output shows errors(memory leak):

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==186749== Memcheck, a memory error detector
==186749== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==186749== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==186749== Command: ./test
==186749== 
==186749== 
==186749== HEAP SUMMARY:
==186749==     in use at exit: 0 bytes in 1 blocks
==186749==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==186749== 
==186749== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==186749==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==186749==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==186749==    by 0x10916D: main (test1.c:5)
==186749== 
==186749== LEAK SUMMARY:
==186749==    definitely lost: 0 bytes in 1 blocks
==186749==    indirectly lost: 0 bytes in 0 blocks
==186749==      possibly lost: 0 bytes in 0 blocks
==186749==    still reachable: 0 bytes in 0 blocks
==186749==         suppressed: 0 bytes in 0 blocks
==186749== 
==186749== For lists of detected and suppressed errors, rerun with: -s
==186749== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

why this is happening even i didnt allocate anything?

edits: compiled this with -std=c11 -g:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define ASSERT_ERROR_PREFIX "Assertion "
#define ASSERT_ERROR_SUFFIX " failed\n"

#define assert(x, num) { \
    if(!(x)) { \
        write(STDOUT_FILENO, ASSERT_ERROR_PREFIX, strlen(ASSERT_ERROR_PREFIX)); \
        write(STDOUT_FILENO, num, sizeof(char)); \
        write(STDOUT_FILENO, ASSERT_ERROR_SUFFIX, strlen(ASSERT_ERROR_SUFFIX)); \
    } \
}

int main () {
    int *p = NULL;
    p = realloc(p, 0);
    *p = '1';
    assert((*p == '1'), "1");
    assert((p == NULL), "2");
    return 0;
}

output: Assertion 2 failed

the valgrind output:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==190041== Memcheck, a memory error detector
==190041== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==190041== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==190041== Command: ./test
==190041== 
==190041== Invalid write of size 4
==190041==    at 0x109196: main (test1.c:19)
==190041==  Address 0x4a5f040 is 0 bytes after a block of size 0 alloc'd
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
==190041== Invalid read of size 4
==190041==    at 0x1091A0: main (test1.c:20)
==190041==  Address 0x4a5f040 is 0 bytes after a block of size 0 alloc'd
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
Assertion 2 failed
==190041== 
==190041== HEAP SUMMARY:
==190041==     in use at exit: 0 bytes in 1 blocks
==190041==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==190041== 
==190041== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
==190041== LEAK SUMMARY:
==190041==    definitely lost: 0 bytes in 1 blocks
==190041==    indirectly lost: 0 bytes in 0 blocks
==190041==      possibly lost: 0 bytes in 0 blocks
==190041==    still reachable: 0 bytes in 0 blocks
==190041==         suppressed: 0 bytes in 0 blocks
==190041== 
==190041== For lists of detected and suppressed errors, rerun with: -s
==190041== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

How realloc works passed 0 size as argument?

from man page: Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc(), or realloc().

Why it needs to be?

compile this with gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 with no options(flags)

this sample

#include <stdlib.h>

int main () {
    int *p = malloc(0);
    p = realloc(p, 0);
    return 0;
}

this is working code, checking memory with valgrind show this:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==185872== Memcheck, a memory error detector
==185872== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==185872== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==185872== Command: ./test
==185872== 
==185872== 
==185872== HEAP SUMMARY:
==185872==     in use at exit: 0 bytes in 0 blocks
==185872==   total heap usage: 1 allocs, 1 frees, 0 bytes allocated
==185872== 
==185872== All heap blocks were freed -- no leaks are possible
==185872== 
==185872== For lists of detected and suppressed errors, rerun with: -s
==185872== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 

but then compile this(added flag -g):

#include <stdlib.h>

int main () {
    int *p = NULL;
    p = realloc(p, 0);
    return 0;
}

valgrind output shows errors(memory leak):

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==186749== Memcheck, a memory error detector
==186749== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==186749== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==186749== Command: ./test
==186749== 
==186749== 
==186749== HEAP SUMMARY:
==186749==     in use at exit: 0 bytes in 1 blocks
==186749==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==186749== 
==186749== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==186749==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==186749==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==186749==    by 0x10916D: main (test1.c:5)
==186749== 
==186749== LEAK SUMMARY:
==186749==    definitely lost: 0 bytes in 1 blocks
==186749==    indirectly lost: 0 bytes in 0 blocks
==186749==      possibly lost: 0 bytes in 0 blocks
==186749==    still reachable: 0 bytes in 0 blocks
==186749==         suppressed: 0 bytes in 0 blocks
==186749== 
==186749== For lists of detected and suppressed errors, rerun with: -s
==186749== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

why this is happening even i didnt allocate anything?

edits: compiled this with -std=c11 -g:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define ASSERT_ERROR_PREFIX "Assertion "
#define ASSERT_ERROR_SUFFIX " failed\n"

#define assert(x, num) { \
    if(!(x)) { \
        write(STDOUT_FILENO, ASSERT_ERROR_PREFIX, strlen(ASSERT_ERROR_PREFIX)); \
        write(STDOUT_FILENO, num, sizeof(char)); \
        write(STDOUT_FILENO, ASSERT_ERROR_SUFFIX, strlen(ASSERT_ERROR_SUFFIX)); \
    } \
}

int main () {
    int *p = NULL;
    p = realloc(p, 0);
    *p = '1';
    assert((*p == '1'), "1");
    assert((p == NULL), "2");
    return 0;
}

output: Assertion 2 failed

the valgrind output:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==190041== Memcheck, a memory error detector
==190041== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==190041== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==190041== Command: ./test
==190041== 
==190041== Invalid write of size 4
==190041==    at 0x109196: main (test1.c:19)
==190041==  Address 0x4a5f040 is 0 bytes after a block of size 0 alloc'd
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
==190041== Invalid read of size 4
==190041==    at 0x1091A0: main (test1.c:20)
==190041==  Address 0x4a5f040 is 0 bytes after a block of size 0 alloc'd
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
Assertion 2 failed
==190041== 
==190041== HEAP SUMMARY:
==190041==     in use at exit: 0 bytes in 1 blocks
==190041==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==190041== 
==190041== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
==190041== LEAK SUMMARY:
==190041==    definitely lost: 0 bytes in 1 blocks
==190041==    indirectly lost: 0 bytes in 0 blocks
==190041==      possibly lost: 0 bytes in 0 blocks
==190041==    still reachable: 0 bytes in 0 blocks
==190041==         suppressed: 0 bytes in 0 blocks
==190041== 
==190041== For lists of detected and suppressed errors, rerun with: -s
==190041== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
Share Improve this question edited yesterday Force Security asked yesterday Force SecurityForce Security 11 silver badge1 bronze badge New contributor Force Security is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 5
  • 1 Which version of C are you using? new_size=0 is undefined behavior in C23 and even before that you might not get a NULL in which case I assume valgrind is assuming you need to free that zero-sized allocation. Maybe you can debug by printing out the results of the realloc. – Paul Hankin Commented yesterday
  • version std c11 – Force Security Commented yesterday
  • I think the answer is not to realloc with new_size=0. If you read the specification of the function you'll realize it's basically impossible to use correctly unless you know specifically how it's going to behave under your compiler version + c version + std library implementation + choice of compiler flags. – Paul Hankin Commented yesterday
  • i understand it, think it is undefined behavior – Force Security Commented yesterday
  • It may be convenient to call realloc via a wrapper function that when the new size is 0, forces the old block (if any) to be freed and no new block to be allocated, e.g. void *my_realloc(void *p, size_t newsize) { if (newsize) { p = realloc(p, newsize); } else { free(p); p = NULL; } return p; }. Caller should not attempt to use the old block if the wrapper returns NULL and the new size was 0, but can assume the wrapper freed the old block in that case. – Ian Abbott Commented 4 hours ago
Add a comment  | 

1 Answer 1

Reset to default 2

Update your Valgrind!

You should use a more recent version of Valgrind. Since version 3.21 it has included a check for realloc of size 0.

You will now get errors like

==3891215== realloc() with size 0
==3891215==    at 0x40440FB: realloc (vg_replace_malloc.c:1801)
==3891215==    by 0x4011AE: main (realloc_size_zero.c:11)
==3891215==  Address 0x4e08040 is 0 bytes inside a block of size 1,024 alloc'd
==3891215==    at 0x403C7B2: malloc (vg_replace_malloc.c:446)
==3891215==    by 0x401187: main (realloc_size_zero.c:8)

realloc size 0 is now UB

Since C23 realloc of size 0 has been made UB (undefined behaviour). Prior to that it was Implementation Defined (and still unsafe to use portably).

The problem with realloc size 0 is that you can't easily tell what it is doing. Some implementations will free the memory, others may do nothing or free the memory then allocate a minimum sized block. Since realloc is replaceable you might have a different behaviour between your development environment and the deployment environment. For instance, if you develop on Linux with GNU libc your users could use LD_PRELOAD with the snmalloc library.

You can use the --show-realloc-size-zero=yes option to turn off this check. If you do that then you may also need to use --realloc-zero-bytes-frees=yes (or no) to try to get Valgrind to match the behaviour of the libc or allocator library that you are using.

What does a 0 size alloc do?

The next thing that you are missing is what the behaviour of malloc of size 0 is (and also realloc when ptr is NULL). That's implementation defined. It will either return a NULL pointer or a pointer to allocate some memory that you are not supposed to access.

So, your first example

  • allocates a small block
  • frees it with realloc

and your second example

  • allocates a small block which leaks

and your third example

  • allocates a small block which is inaccessible
  • writes to it causing an error
  • reads from it causing an error
  • leaks the allocated block

Advice

Don't allow realloc of size 0 in your code.

I know, 'cos I was there -- Max Boyce

本文标签: linuxHow realloc works passed 0 size as argumentStack Overflow