1

I have a code like this :

void **array = (void**)malloc(sizeof(void*)*4);
array[0] = (void *)"Hello";
array[1] = (void *)"World";
array[2] = (void *)"My";
array[3] = (void *)"Example";
array = (void **)realloc(array,sizeof(void*)*3);

printf("%s\n",(char*)array[3]);
free(array);

Even though I reallocate the memory it is printing Example

Where am I wrong?

Spikatrix
  • 20,225
  • 7
  • 37
  • 83
C Learner
  • 35
  • 3
  • 3
    I think it is because of UB. Side note: [don't cast the result of `malloc`/`calloc`/`realloc` in C](http://www.stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). – Spikatrix May 09 '15 at 13:45
  • I have not casted but still the same thing happens – C Learner May 09 '15 at 13:47
  • That's why it is a *side note*. It is not related to the issue. – Spikatrix May 09 '15 at 13:48
  • As far as I know as long as `realloc()` gives you at least the amount of memory you requested there is nothing to stop it from just giving you the same memory area back if the request is for something smaller. That would actually be easier on the `realloc()` functionality. – Richard Chambers May 09 '15 at 13:48
  • @CoolGuy What is UB?Can you explain – C Learner May 09 '15 at 13:48
  • "UB" = Undefined Behaviour – alk May 09 '15 at 13:50
  • @RichardChambers What is the solution then – C Learner May 09 '15 at 13:50
  • UB = [Undefined Behavior](http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html). It means that anything can happen if you attempt it. Results range from crashes,segfaults to surprisingly weird results. The code, could work, may not, or do something else.([Wikipedia article](http://en.wikipedia.org/wiki/Undefined_behavior)). As per the C11 standard, UB is "behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements". Basically, you should not rely on this "astonishing" behavior. – Spikatrix May 09 '15 at 13:51
  • UB is Undefined Behavior meaning that the C Standard says that what happens with a particular action may vary depending on how the compiler and C runtime is written. It means that different compilers are free to provide different behavior because the action is defined to be outside of the scope of the standard. – Richard Chambers May 09 '15 at 13:51
  • 5
    The solution is to stop referencing memory that is beyond the end of your array. – David Hoelzer May 09 '15 at 13:51
  • 1
    The solution is to always keep track on how much memory had been allocated to a certain pointer and **never** access memory before or beyond those bounds. If doing so UB is invoked and from this moment on **anything** can happen. The program is not on the save side anymore. – alk May 09 '15 at 13:52
  • Since yoiu are experimenting, what I would do is to use very different sizes as in create one of say size 5 then go to something say size 100 and then to something size 3 and see what happens. I would expect that doing something like that would cause a different area of memory to be allocated. – Richard Chambers May 09 '15 at 13:53
  • @DavidHoelzer Is it not a memory leak? – C Learner May 09 '15 at 13:53
  • @CLearner , No. Memory, once out of your hands, need not be changed at that spot itself. See [this awesome answer](http://stackoverflow.com/a/6445794/3049655) – Spikatrix May 09 '15 at 13:54
  • @CoolGuy: "*once out of your hands*" sounds quiet ambiguous to me, as one also might just have lost the last reference to it without having `free()`ed it before, which infact **would** be a memory leak then. To better word: "*Memory, once freed, needs not ...*". – alk May 09 '15 at 13:56
  • @CLearner No, it's not a leak. It's a use after free, which is very bad. – David Hoelzer May 09 '15 at 13:58
  • 1
    I stil wonder why anyone would cast away type checks. This use of `void` is just ... well ... uncommon (to say the least). Sorry, but if any of my students had given me this, he would have gotten quite some homework to do. – too honest for this site May 09 '15 at 14:05
  • Did you expect a segmentation fault? Access to freed memory does not have the defined behaviour of causing a segmentation fault. In your case the undefined behaviour is that it prints out "Example" because the freed memory is not returned to the operating system and is not being altered by the `realloc`. – 4566976 May 09 '15 at 15:07

3 Answers3

3

It works because sometimes undefined behaviour actually does work. That still doesn't make it a good idea because it's not guaranteed to work.

What's likely happening under the covers is that the realloc call is not changing anything at all because you've asked for a small reduction in a block that's already insignificantly small anyway.

Many memory allocation functions have a certain resolution which they allocate to, such as always a multiple of sixteen bytes. So, in that case, whether you asked for three four-byte pointers or four four-byte pointers, you'll always get a sixteen-byte chunk (plus overhead). It also means that, if you tell it to reduce your sixteen-byte chunk to a twelve-byte chunk, it's probably smart enough to just leave it alone.

You can check this by printing out the pointer just before and just after the realloc - it's likely to be exactly the same value.

So it just leaves the memory alone which is why, when you access the fourth element of the array, it's still set up as before.

But, as stated, this is an implementation detail that is not guaranteed, so you should not rely on it. Ever!

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
1

See this:

$ cat test.cpp 
#include <stdlib.h>
#include <stdio.h>

int main() {
        void **array = (void**)malloc(sizeof(void*)*4);
        array[0] = (void *)"Hello";
        array[1] = (void *)"World";
        array[2] = (void *)"My";
        array[3] = (void *)"Example";
        array = (void **)realloc(array,sizeof(void*)*3);

        printf("%s\n",(char*)array[3]);
        free(array);
}
$ g++ test.cpp -o a -fsanitize=address                                                                                    
$ ./a                                                                                                                     
=================================================================                                                                                           
==11051== ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60060000efc8 at pc 0x400a3f bp 0x7fffafc0bf40 sp 0x7fffafc0bf38                        
READ of size 8 at 0x60060000efc8 thread T0                                                                                                                  
    #0 0x400a3e (/tmp/a+0x400a3e)                                                                                                                           
    #1 0x7fe6d321aec4 (/lib/x86_64-linux-gnu/libc-2.19.so+0x21ec4)                                                                                          
    #2 0x400868 (/tmp/a+0x400868)                                                                                                                           
0x60060000efc8 is located 0 bytes to the right of 24-byte region [0x60060000efb0,0x60060000efc8)                                                            
allocated by thread T0 here:                                                                                                                                
    #0 0x7fe6d35d355f (/usr/lib/x86_64-linux-gnu/libasan.so.0.0.0+0x1555f)                                                                                  
    #1 0x400a12 (/tmp/a+0x400a12)                                                                                                                           
    #2 0x7fe6d321aec4 (/lib/x86_64-linux-gnu/libc-2.19.so+0x21ec4)                                                                                          
Shadow bytes around the buggy address:                                                                                                                      
  0x0c013fff9da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
=>0x0c013fff9df0: fa fa fa fa fa fa 00 00 00[fa]fa fa fd fd fd fd                                                                                           
  0x0c013fff9e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
  0x0c013fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa                                                                                           
Shadow byte legend (one shadow byte represents 8 application bytes):                                                                                        
  Addressable:           00                                                                                                                                 
  Partially addressable: 01 02 03 04 05 06 07                                                                                                               
  Heap left redzone:     fa                                                                                                                                 
  Heap righ redzone:     fb                                                                                                                                 
  Freed Heap region:     fd                                                                                                                                 
  Stack left redzone:    f1                                                                                                                                 
  Stack mid redzone:     f2                                                                                                                                 
  Stack right redzone:   f3
  Stack partial redzone: f4
  Stack after return:    f5
  Stack use after scope: f8
  Global redzone:        f9
  Global init order:     f6
  Poisoned by user:      f7
  ASan internal:         fe
==11051== ABORTING

So, actually these bytes of memory were freed, but you are just lucky (or not) that these bytes wasn't reused for anything else.

The realloc call changes size of an allocated memory. In your case you lowered a size, so the system just cut off the bytes in contiguous region that were allocated previously. I.e. your pointer to the text is still there, just the system didn't yet reused these bytes for another thing. In my example I compiled your code with debug option -fsanitize=address that checks for buffer overflow and an usage of a freed memory, and terminates an app with an error message if something alike happened — and it happens in your case.

Hi-Angel
  • 4,933
  • 8
  • 63
  • 86
1

Try this:

printf( "before: %p\n", array );
array = (void **)realloc(array,sizeof(void*)*3);
printf( "after: %p\n", array );

What's likely happening is the heap implementation you're using notices you're just dropping a few bytes from your allocation and doesn't do anything other than marking the current block a bit smaller before returning the same pointer.

I'd guessing you're running a 32-bit application on an x86 CPU. So your pointers are probably 4 bytes, but the x86 has some data types that have an 8-byte alignment restriction. That's important because 7.20.3 of the C standard says:

The order and contiguity of storage allocated by successive calls to the 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 and then used to access such an object or an array of such objects in the space allocated...

That means in practice most malloc()/calloc()/realloc()/free() implementations on x86 machines return memory in 8-byte chunks. Since your chunk started at 16 bytes - or two eight-byte chunks - and you shortened it to 12 - which is still two eight-byte chunks - your realloc() call didn't move the memory.

In other words, undefined behavior. Don't expect that to work if you start with 2000 pointers in your array and you realloc() that down to two pointers.

Spikatrix
  • 20,225
  • 7
  • 37
  • 83
Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • [Don't cast the result of malloc/calloc/realloc in C‌​](http://www.stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) – Spikatrix May 09 '15 at 14:24