C语言中与realloc的混淆

Confusion with realloc in C language

我有这样的代码:

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);

尽管我重新分配了正在打印的内存Example

我哪里错了?

看到这个:

$ 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

所以,实际上这些字节的内存被释放了,但你很幸运(或没有)这些字节没有被重新用于其他任何事情。

realloc 调用更改已分配内存的大小。在您的情况下,您降低了一个大小,因此系统只是切断了先前分配的连续区域中的字节。 IE。您指向文本的指针仍然存在,只是系统尚未将这些字节重新用于另一件事。在我的示例中,我使用调试选项 -fsanitize=address 编译了您的代码,该选项检查缓冲区溢出和已释放内存的使用情况,并在发生类似情况时终止应用程序并显示错误消息 - 它发生在您的情况下。

之所以有效,是因为有时未定义的行为实际上 有效。这仍然不是一个好主意,因为它不能保证工作。

幕后可能发生的事情是 realloc 调用根本没有改变任何东西,因为您要求对一个本来就已经微不足道的块进行小幅缩减。

许多内存分配函数都有特定的分配分辨率,例如总是十六字节的倍数。因此,在那种情况下,无论您要求三个四字节指针还是四个四字节指针,您总是会得到一个十六字节的块(加上开销)。这也意味着,如果您告诉它把 16 字节的块减少到 12 字节的块,它可能足够聪明,不会管它。

您可以通过打印 realloc 之前和之后的指针来检查这一点 - 它可能是完全相同的值。

所以它只保留内存,这就是为什么当您访问数组的第四个元素时,它仍然像以前一样设置。

但是,如前所述,这是一个保证的实施细节,因此您不应依赖它。 永远!

试试这个:

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

可能发生的情况是您正在使用的堆实现注意到您只是从分配中删除了几个字节,除了在 returning 之前将当前块标记为更小之外没有做任何其他事情相同的指针。

我猜您是 运行 x86 CPU 上的 32 位应用程序。所以你的指针可能是 4 个字节,但是 x86 有一些数据类型有 8 个字节的对齐限制。这很重要,因为 C standard 的 7.20.3 说:

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...

这意味着实际上大多数 malloc()/calloc()/realloc()/free() x86 机器上的实现 return 8 字节块内存。由于您的块从 16 字节开始 - 或者两个八字节块 - 并且您将其缩短为 12 - 这仍然是两个八字节块 - 您的 realloc() 调用没有移动内存。

换句话说,未定义的行为。如果您从数组中的 2000 个指针开始并且您 realloc() 减少到两个指针,请不要指望它会起作用。