alloc()、malloc()、calloc()、realloc()区别及用法

本文详细介绍了C语言中与内存管理相关的函数,包括malloc、calloc、realloc等,对比了它们的功能和工作原理,如malloc和calloc在内存初始化上的区别,以及realloc在调整已分配内存空间大小时的不同情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C语言跟内存申请相关的函数主要有 alloca,calloc,malloc,free,realloc,sbrk等.

函数malloc()和calloc()都可以用来动态分配内存空间,但两者稍有区别。 
malloc()函数有一个参数,即要分配的内存空间的大小:     

       void *malloc(size_t size);

calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。 

       void *calloc(size_t numElements, size_t sizeOfElement); 

如果调用成功,函数malloc()和函数calloc()都将返回所分配的内存空间的首地址。

  函数malloc()和函数calloc()的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由malloc()函数分配的内存空间原来没有 被使用过,则其中的每一位可能都是0;反之,如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据。也就是说,使用malloc()函数的程序开 始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题。 需要调用函数memset来初始化这部分的内存空间.

  malloc()函数的工作机制

  malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。


  函数calloc()会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那麽这些元素将保证会被初始 化为0;如果你是为指针类型的元素分配内存,那麽这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零。

  alloca是向栈申请内存,因此无需释放. alloc与afree以栈的方式(即后进先出的列表)进行存储空间的管理。

  realloc(void *__ptr, size_t __size):更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小。

  如果将分配的内存减少,realloc仅仅是改变索引的信息。

  如果是将分配的内存扩大,则有以下情况:
  1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
  2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
  3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。

  注意:如果调用成功,不管当前内存段后面的空闲空间是否满足要求,都会释放掉原来的指针,重新返回一个指针,虽然返回的指针有可能和原来的指针一样,即不能再次释放掉原来的指针。

<think>我们正在讨论一个关于动态库方式替换malloc/calloc时无法捕捉调用的问题。用户提到在使用动态库替换这些函数时遇到了无法捕捉调用的问题。根据引用[1]和引用[2],我们知道malloccallocrealloc函数是动态内存分配的基础,并且它们的失败必须被检查,否则会导致严重问题。 用户的问题可能是:在尝试通过动态库替换标准库中的malloccalloc函数时,发现替换没有生效,或者无法捕捉到对这些函数的调用。这通常发生在动态链接的环境中,可能的原因包括链接顺序、符号覆盖问题,或者程序内部直接使用了其他内存分配函数。 根据引用[1],我们了解到malloccallocrealloc区别。引用[2]则强调了检查这些函数返回值的重要性,以避免空指针解引用等安全问题。 解决这个问题,我们需要考虑动态链接库中符号替换的机制。在Linux系统中,使用LD_PRELOAD环境变量可以预加载一个动态库,从而覆盖标准库中的符号。但是,如果替换没有生效,可能的原因包括: 1. 动态库没有正确导出malloccalloc等符号。 2. 程序内部直接使用了其他内存分配函数(如realloc、aligned_alloc等),而这些函数没有被替换。 3. 链接顺序问题,可能标准库在预加载库之后被加载,导致预加载的符号被覆盖(这通常不会发生,因为预加载库的符号优先级更高)。 4. 程序使用了静态链接,或者使用了其他内存分配库(如jemalloc、tcmalloc)等。 为了捕捉对malloc/calloc的调用,我们可以通过LD_PRELOAD加载一个自定义的动态库,该动态库中实现了malloccalloc函数,并在其中加入日志或钩子(hook)功能。但是,如果这些调用没有被捕捉到,可能是由于上述原因之一。 下面,我们将逐步分析并解决这个问题。 ### 步骤1:确保动态库正确导出符号 自定义动态库必须正确定义并导出malloccalloc函数。例如,一个简单的动态库示例: ```c // mymalloc.c #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> void *malloc(size_t size) { static void *(*real_malloc)(size_t) = NULL; if (!real_malloc) { real_malloc = dlsym(RTLD_NEXT, "malloc"); } void *ptr = real_malloc(size); printf("malloc(%zu) = %p\n", size, ptr); return ptr; } void *calloc(size_t num, size_t size) { static void *(*real_calloc)(size_t, size_t) = NULL; if (!real_calloc) { real_calloc = dlsym(RTLD_NEXT, "calloc"); } void *ptr = real_calloc(num, size); printf("calloc(%zu, %zu) = %p\n", num, size, ptr); return ptr; } ``` 编译动态库: ```bash gcc -shared -fPIC -o libmymalloc.so mymalloc.c -ldl ``` ### 步骤2:使用LD_PRELOAD加载动态库 运行程序时,使用: ```bash LD_PRELOAD=./libmymalloc.so ./your_program ``` ### 步骤3:检查是否捕捉到调用 如果此时仍然没有捕捉到调用,可能的原因有: - 程序内部使用了其他内存分配函数(如realloc、aligned_alloc等),需要我们在动态库中也替换这些函数。 - 程序可能直接使用了系统调用(如brk或mmap),但这种情况较少见,因为标准库函数通常都是基于这些系统调用。 - 程序可能静态链接了内存分配函数,此时LD_PRELOAD无法替换静态链接的符号。 ### 步骤4:扩展动态库以覆盖更多函数 我们可以将其他内存分配函数也进行替换,例如realloc、free、aligned_alloc等。 ```c void *realloc(void *ptr, size_t size) { static void *(*real_realloc)(void *, size_t) = NULL; if (!real_realloc) { real_realloc = dlsym(RTLD_NEXT, "realloc"); } void *new_ptr = real_realloc(ptr, size); printf("realloc(%p, %zu) = %p\n", ptr, size, new_ptr); return new_ptr; } void free(void *ptr) { static void (*real_free)(void *) = NULL; if (!real_free) { real_free = dlsym(RTLD_NEXT, "free"); } real_free(ptr); printf("free(%p)\n", ptr); } ``` ### 步骤5:处理C++中的操作符new和delete 如果程序是C++编写的,那么new和delete操作符也会调用内存分配函数。但是,这些操作符在C++中通常是通过malloc和free实现的,所以我们替换malloc和free后,new和delete也会被记录。但是,为了确保覆盖,我们也可以重载new和delete操作符(但这需要在C++代码中显式重载,无法通过LD_PRELOAD实现,因为它们是C++符号,名字修饰不同)。因此,对于C++程序,可能需要同时替换malloc/free和重载new/delete。 ### 步骤6:检查动态库加载情况 使用`ldd`命令检查程序依赖的动态库,确认我们的动态库被正确预加载。 ### 步骤7:使用工具追踪 如果上述方法仍然无效,可以使用系统工具如`strace`或`ltrace`来跟踪系统调用和库函数调用,以确定程序实际调用了哪些函数。 例如,使用ltrace: ```bash ltrace -e malloc,free,calloc,realloc ./your_program ``` ### 总结 动态库替换malloc/calloc无法捕捉调用的问题,通常是由于动态库没有覆盖所有内存分配函数,或者程序使用了静态链接、其他分配库等原因。通过扩展动态库覆盖更多函数,并确保正确使用LD_PRELOAD,可以解决大部分问题。 如果问题仍然存在,可能需要考虑程序本身是否绕过了标准库的内存分配函数,或者使用了自定义的内存分配器。 ### 相关问题 1. 如何通过LD_PRELOAD替换动态库中的其他函数? 2. 在C++中,如何替换全局的new和delete操作符? 3. 使用LD_PRELOAD时,如何避免与其他预加载库的冲突? 4. 除了LD_PRELOAD,还有哪些方法可以钩住(hook)内存分配函数?</think>### 动态库替换 malloc/calloc 无法捕捉调用的问题分析 #### 问题原因 当使用动态库方式替换标准库的 `malloc`/`calloc` 时,无法捕捉调用通常由以下原因导致: 1. **符号覆盖失效** - 动态库需通过 `LD_PRELOAD` 预加载才能覆盖标准库符号[^1]。若未正确设置环境变量(如 `export LD_PRELOAD=/path/to/your_lib.so`),替换将失效。 - 程序可能静态链接了内存分配函数,导致动态库无法覆盖静态符号。 2. **函数签名不匹配** - 替换函数必须严格匹配标准库签名: ```c void* malloc(size_t size); void* calloc(size_t num, size_t size); ``` 若自定义函数的参数类型或返回类型不匹配(如使用 `int` 代替 `size_t`),链接器不会将其视为有效覆盖。 3. **间接调用问题** - C++ 程序可能通过 `operator new` 分配内存,其底层调用 `malloc`,但若重载了 `operator new` 而未指向自定义 `malloc`,调用仍指向原函数。 - 第三方库(如 OpenSSL)可能使用内部内存分配器,绕过标准库函数。 4. **线程局部存储(TLS)干扰** - 某些分配器(如 tcmalloc)使用线程局部存储加速访问。若替换库未处理 TLS,可能导致原始函数被调用。 --- #### 解决方案 ##### 步骤1:验证动态库加载 确保正确预加载动态库: ```bash # 临时生效 LD_PRELOAD=/path/to/your_malloc.so ./your_program # 永久生效(谨慎使用) export LD_PRELOAD=/path/to/your_malloc.so ./your_program ``` 使用 `ldd` 检查依赖: ```bash ldd your_program | grep malloc # 应显示 your_malloc.so ``` ##### 步骤2:确保函数签名正确 自定义动态库示例: ```c // your_malloc.c #include <dlfcn.h> #include <stdio.h> void* malloc(size_t size) { static void* (*real_malloc)(size_t) = NULL; if (!real_malloc) { real_malloc = dlsym(RTLD_NEXT, "malloc"); // 获取标准库函数 } void* ptr = real_malloc(size); printf("malloc(%zu) = %p\n", size, ptr); // 捕捉调用 return ptr; } ``` 编译为动态库: ```bash gcc -shared -fPIC -o your_malloc.so your_malloc.c -ldl ``` ##### 步骤3:处理 C++ 和第三方库 - **C++ 程序**:需额外覆盖 `operator new`: ```c++ void* operator new(size_t size) { return your_custom_malloc(size); } ``` - **第三方库**:通过 `dlsym` 强制重定向: ```c void* (*original_openssl_malloc)(size_t) = NULL; void* custom_openssl_malloc(size_t size) { if (!original_openssl_malloc) { original_openssl_malloc = dlsym(RTLD_NEXT, "CRYPTO_malloc"); } return your_custom_malloc(size); // 指向你的实现 } ``` ##### 步骤4:验证内存分配失败处理 引用[2]强调必须检查分配结果: ```c void* ptr = malloc(1e9); // 大内存可能失败 if (ptr == NULL) { // 必须检查空指针! perror("malloc failed"); exit(EXIT_FAILURE); } ``` 在替换函数中同样需实现返回值检查逻辑。 --- #### 调试技巧 1. **使用 `strace` 跟踪系统调用**: ```bash strace -e brk,mmap,mprotect ./your_program ``` 2. **通过 GDB 拦截调用**: ```gdb break __libc_malloc run backtrace ``` 3. **日志增强**:在替换函数中添加调用栈打印: ```c #include <execinfo.h> void print_stack() { void* buffer[10]; backtrace(buffer, 10); backtrace_symbols_fd(buffer, 10, STDOUT_FILENO); } ``` --- ### 相关问题 1. **如何确保自定义内存分配器的线程安全性?** - 需实现锁机制或使用原子操作,避免多线程竞争。 2. **`LD_PRELOAD` 失效的常见场景有哪些?** - SUID 程序(权限隔离)、静态链接二进制、容器环境(如 Docker 需额外配置)。 3. **替换 `realloc` 时如何避免内存泄漏?** - 必须正确处理旧指针的释放和新内存块的迁移逻辑。 4. **动态库替换对程序性能的影响如何评估?** - 可通过 `perf` 工具对比替换前后的指令周期和缓存命中率。 [^1]: 动态内存分配函数区别与使用要点 [^2]: 未检查内存分配返回值的风险及后果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值