首页 > 系统相关 >动态内存分配之realloc()函数详解

动态内存分配之realloc()函数详解

时间:2024-09-04 12:49:47浏览次数:5  
标签:size realloc 详解 内存 动态内存 NULL ptr 指针

目录

一、函数简介

二、函数原型

参数

返回值

三、函数实现(伪代码)

3.1. 简化的 realloc 实现逻辑

3.2. 伪代码示例

四、使用场景

4.1. 动态数组大小调整

4.2. 动态字符串大小调整

4.3. 内存优化

4.4. 复杂数据结构的内存管理

4.5. 跨函数内存管理

4.6. 灵活的内存分配策略

五、使用注意事项

5.1. 检查返回值

5.2. 更新指针

5.3. 内存泄漏

5.4. 缩容问题

5.5. 传递正确的指针

5.6. 初始化和未定义行为

5.7. 性能

5.7. 释放内存

5.8. 调试和错误处理

六、示例代码


一、函数简介

realloc 函数是 C 语言标准库中用于动态内存管理的一个重要函数,它允许程序调整之前已经通过 malloccalloc 或 realloc 函数分配的内存块的大小。这个函数定义在 <stdlib.h> 头文件中。

二、函数原型

void* realloc(void* ptr, size_t size);

参数

  • ptr:指向要重新分配的内存块的指针。如果这是一个空指针,realloc() 的行为就像 malloc() 一样,分配一块新的内存块。如果这不是空指针,那么它应该是指向一个之前通过 malloc()calloc() 或 realloc() 分配的内存块的指针。
  • size:新的内存块的大小,以字节为单位。

返回值

  • 如果 realloc() 成功,它返回一个指向新内存块的指针,这个内存块的大小为 size,内容被初始化为原内存块的内容(直到较小者的大小),新的未初始化部分的值是未定义的。如果 ptr 是空指针,realloc() 返回一个指向新分配的内存的指针,其行为类似于 malloc()
  • 如果 realloc() 失败(例如,由于内存不足),它返回空指针(NULL),并且原内存块(如果 ptr 不是 NULL)的内容保持不变,不会被释放。

三、函数实现(伪代码)

realloc 函数的实现是底层操作,依赖于操作系统的内存管理机制,且具体实现可能会因不同的操作系统、编译器和运行时库而异。然而,我们可以提供一个简化的、概念性的 realloc 实现逻辑,以帮助理解它是如何工作的。

请注意,下面的实现是为了教学目的而简化的,并且不包括错误处理、线程安全或其他重要的现实世界考虑因素。

3.1. 简化的 realloc 实现逻辑

  • 检查指针是否为 NULL
    • 如果 ptr 是 NULL,则 realloc 表现得像 malloc,分配一个新的内存块。
  • 检查新大小是否为 0
    • 如果 size 是 0,并且 ptr 不是 NULL,则 realloc 可能会释放 ptr 指向的内存块并返回 NULL(具体行为可能因实现而异)。但这里我们假设它不释放内存,并返回 NULL(这是 POSIX 的行为)。
  • 尝试在原位置扩展内存
    • 如果可能,系统尝试在原内存块之后扩展足够的空间来满足新的大小要求。
    • 这通常涉及调用操作系统提供的扩展内存区域的功能(如果存在)。
  • 如果扩展失败,则分配新内存并复制数据
    • 如果在原位置扩展内存失败,realloc 会:
      • 分配一个新的、足够大的内存块。
      • 将原内存块的内容复制到新内存块中(注意只复制原内存块大小和新大小中较小者的内容)。
      • 释放原内存块。
  • 返回新内存块的指针
    • 无论通过哪种方式,realloc 都将返回指向新内存块的指针(如果分配成功),或者 NULL(如果分配失败)。

3.2. 伪代码示例

// 注意:这不是一个有效的、可编译的 C 代码实现。  
void* simple_realloc(void* ptr, size_t size) {  
    if (ptr == NULL) {  
        // 如果 ptr 是 NULL,则表现为 malloc  
        return malloc(size);  
    }  
  
    if (size == 0) {  
        // 根据 POSIX 标准,如果 ptr 不是 NULL 且 size 是 0,则释放 ptr 并返回 NULL  
        free(ptr);  
        return NULL;  
    }  
  
    // 尝试在原位置扩展内存(伪代码)  
    void* extended_ptr = try_extend_memory(ptr, size);  
    if (extended_ptr != NULL) {  
        // 扩展成功,返回新指针(在这种情况下,它可能与原指针相同)  
        return extended_ptr;  
    }  
  
    // 扩展失败,分配新内存并复制数据  
    void* new_ptr = malloc(size);  
    if (new_ptr != NULL) {  
        // 注意:这里应该只复制 min(原大小, 新大小) 的数据  
        // 但为了简化,我们假设我们知道原大小并且它是足够大的  
        memcpy(new_ptr, ptr, /* 假设我们知道原大小 */);  
        // 释放原内存块  
        free(ptr);  
        return new_ptr;  
    }  
  
    // 如果新内存分配也失败,则返回 NULL  
    return NULL;  
}  
  
// 注意:try_extend_memory 是一个虚构的函数,用于演示。  
// 在实际中,这样的操作会由运行时库和操作系统合作完成。

重要的是要强调,这个伪代码示例省略了许多细节,并且 try_extend_memory 函数并不存在于任何标准的 C 库中。实际的 realloc 实现将涉及与操作系统的内存管理系统的直接交互,以执行内存分配、释放和(可能)扩展操作。 

四、使用场景

以下是realloc函数的主要使用场景:

4.1. 动态数组大小调整

当需要动态调整数组的大小时,可以使用realloc函数重新分配内存块的大小,从而实现数组的扩展或缩小。这对于处理不确定大小的数据集特别有用,如用户输入、文件读取等场景。

4.2. 动态字符串大小调整

类似于动态数组,当需要动态调整字符串的大小时,realloc函数也非常有用。它可以确保字符串有足够的空间来存储新添加的字符,而无需事先知道字符串的最终大小。

4.3. 内存优化

在需要动态分配内存的程序中,realloc可以用于优化内存使用。例如,如果之前分配的内存块过大或过小,可以通过realloc调整到一个更合适的大小,以节省内存空间或避免内存浪费。

4.4. 复杂数据结构的内存管理

在处理复杂数据结构(如链表、树、图等)时,realloc可以用于动态地调整节点的内存大小。这特别适用于需要动态扩展或缩减数据结构的场景,如动态数组实现的栈、队列等。

4.5. 跨函数内存管理

在多个函数之间共享内存时,realloc可以确保内存块的大小能够适应不同函数的需求。例如,在一个函数中分配内存,然后在另一个函数中扩展或缩小该内存块的大小。

4.6. 灵活的内存分配策略

在某些情况下,程序可能需要根据运行时的情况来决定内存分配的大小。realloc提供了一种灵活的方式来调整内存块的大小,以适应这些动态变化的需求。

realloc函数在动态内存管理的各种场景中都非常有用,但使用时需要注意指针的引用关系和内存安全的问题。

五、使用注意事项

在使用 realloc 函数时,需要注意以下几个关键事项,以确保程序的正确性和稳定性。

5.1. 检查返回值

  • NULL 检查realloc 在内存分配失败时会返回 NULL。因此,每次调用 realloc 后,都应该检查其返回值是否为 NULL,并据此处理错误情况。如果 realloc 返回 NULL,则原内存块(ptr 指向的)保持不变,需要决定是释放原内存块还是采取其他错误处理措施。

5.2. 更新指针

  • 指针更新:由于 realloc 可能会移动内存块(特别是当请求的内存大小大于原大小时),因此必须使用 realloc 返回的新指针来更新原指针。如果 realloc 成功,但原指针没有被更新,那么程序可能会继续访问旧的内存地址,导致未定义行为。

5.3. 内存泄漏

  • 避免内存泄漏:在调用 realloc 后,如果分配成功,原始指针会失效,新的内存地址会被返回。如果这个地址被分配到另一个指针,而原始指针没有及时更新,就可能出现内存泄漏。因此,务必确保在 realloc 成功后更新原始指针。

5.4. 缩容问题

  • 缩容考虑:如果 realloc 请求的内存大小小于原大小,则可能会在原地缩容,也可能会异地缩容。但通常不建议依赖缩容来节省空间,因为这可能会带来性能上的开销(如数据移动)。如果确实需要缩容,应考虑程序的逻辑是否允许这样做,并妥善处理可能的数据丢失问题。

5.5. 传递正确的指针

  • 指针来源:传递给 realloc 的指针必须是先前通过 malloccalloc 或 realloc 分配的。如果传递了一个未初始化的指针或非法指针,realloc 的行为将是未定义的。

5.6. 初始化和未定义行为

  • 新内存未初始化:如果 realloc 分配了新的内存块(即没有在原位置扩展),则新分配的内存块中的新增部分将不会被初始化。因此,在使用这些新增的内存之前,需要手动进行初始化。

5.7. 性能

  • 性能考虑:频繁地调用 realloc() 来逐渐增加内存大小可能会导致性能问题,因为每次调用都可能涉及到内存的移动。在某些情况下,可能需要考虑一次性分配足够的内存或使用其他数据结构。

5.7. 释放内存

  • 释放内存:无论 realloc 是否成功,如果原内存块不再需要,都应该通过调用 free 来释放它。这有助于避免内存泄漏。

5.8. 调试和错误处理

  • 调试和错误处理:在程序开发和调试过程中,可以通过在 realloc 函数处设置断点来跟踪内存分配和释放的情况,以及检查内存操作是否正确。此外,还可以编写错误处理代码来优雅地处理 realloc 失败的情况。。

六、示例代码

下面是一个使用realloc函数的示例代码,该示例展示了如何动态地调整一个整数数组的大小。

#include <stdio.h>  
#include <stdlib.h>  
  
int main() {  
    int *array = NULL; // 初始化为NULL,表示没有分配内存  
    size_t size = 0;   // 当前数组的大小  
    size_t capacity = 0; // 数组的总容量  
  
    // 假设我们要向数组中添加一些整数  
    for (int i = 0; i < 10; ++i) {  
        // 如果数组已满(没有剩余容量),则扩展容量  
        if (size == capacity) {  
            // 容量加倍(这里简单起见,实际应用中可能需要根据情况调整)  
            capacity = capacity == 0 ? 1 : capacity * 2;  
            // 使用realloc重新分配内存  
            void *temp = realloc(array, capacity * sizeof(int));  
            if (temp == NULL) {  
                // 如果realloc失败,则释放原数组并退出  
                free(array);  
                printf("Memory allocation failed!\n");  
                return 1;  
            }  
            array = (int *)temp; // 更新数组指针  
        }  
  
        // 向数组中添加元素  
        array[size++] = i;  
  
        // 打印当前数组的内容(可选)  
        for (size_t j = 0; j < size; ++j) {  
            printf("%d ", array[j]);  
        }  
        printf("\n");  
    }  
  
    // 使用完毕后,释放内存  
    free(array);  
  
    return 0;  
}

在这个示例中,我们首先定义了一个指向int的指针array,并将其初始化为NULL,表示初始时没有分配内存。我们还定义了两个size_t类型的变量sizecapacity,分别用于跟踪数组当前的大小和总容量。

在循环中,我们尝试向数组中添加元素。如果数组已满(即size等于capacity),我们就通过realloc函数扩展数组的容量。这里,我们简单地将容量加倍,但在实际应用中,你可能需要根据具体情况来调整容量的增长策略。

realloc函数的调用可能会移动内存块,因此我们需要将realloc的返回值(一个void*类型的指针)转换为适当的类型(在这个例子中是int*),并更新array指针。如果realloc失败(即返回NULL),我们需要释放原数组的内存(如果存在的话),并处理错误。

在每次向数组添加元素后,我们还打印了当前数组的内容,以便观察数组的变化。

最后,在程序结束前,我们使用free函数释放了数组的内存,以避免内存泄漏。

标签:size,realloc,详解,内存,动态内存,NULL,ptr,指针
From: https://blog.csdn.net/weixin_37800531/article/details/141882645

相关文章

  • 【技术详解】Java泛型:全面解析与实战应用(进阶版)
    文章目录Java泛型:全面解析与实战应用1.引言1.1什么是Java泛型?1.2泛型的历史背景1.3泛型的重要性与优势2.泛型的基本概念2.1类型参数2.2泛型类2.3泛型方法2.4泛型接口2.5泛型擦除3.创建和使用泛型类3.1定义一个简单的泛型类3.2使用泛型类3.3泛型类的类型......
  • 09J621-2《电动采光排烟天窗》技术详解
    09J621-2《电动采光排烟天窗》图集是建筑通风排烟设计中的重要参考资料,是对04J621-2《电动采光排烟天窗》的修编,发行于2009年12月。该图集提供了五种类型的电动采光排烟天窗,包括三角形、一字形、圆拱形、避风形以及侧开形,以满足不同建筑的需求。一、天窗类型及特点(一)三角形......
  • Spring Boot中的自定义事件详解与实战
    一、Spring事件机制概述1.1什么是Spring事件Spring事件机制是一种基于发布-订阅模式的事件驱动机制,允许组件之间进行松散耦合的通信。通过发布事件,其他监听该事件的组件能够做出响应,从而实现不同模块之间的协作。Spring事件的核心概念包括以下三个部分:事件(Event):表示发......
  • 07 Midjourney从零到商用·基础篇:参数合集详解
    在使用Midjourney生成图片时,除了Prompt(提示词)要写好之外,Parameters(后缀参数)也是非常重要的一部分。它可以帮助我们更加精确地控制图像生成的方式,例如:图像的宽高比、风格化程度、完成度等等,是提高AI绘画能力必须了解的部分。因此,今天我们将系统学习下后缀参数的使用方法......
  • 神经网络之卷积篇:详解池化层(Pooling layers)
    详解池化层除了卷积层,卷积网络也经常使用池化层来缩减模型的大小,提高计算速度,同时提高所提取特征的鲁棒性,来看一下。先举一个池化层的例子,然后再讨论池化层的必要性。假如输入是一个4×4矩阵,用到的池化类型是最大池化(maxpooling)。执行最大池化的树池是一个2×2矩阵。执行过程......
  • Vue3组件通信详解
    Vue3中的组件通讯是Vue应用开发中非常重要的一环,它允许组件之间传递数据和方法,从而实现数据的共享和功能的调用。下面将分别介绍父子组件、孙子组件(祖孙组件)、兄弟组件之间的通讯方式,并给出示例代码和总结表格。一、父子组件通讯1.父传子(props)父组件通过props向子组......
  • jstack命令详解【转】
    jstack命令详解  简介jstack命令用于打印指定Java进程、核心文件或远程调试服务器的Java线程的Java堆栈跟踪信息[1]。jstack命令可以生成JVM当前时刻的线程快照。线程快照是当前JVM内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿......
  • Linux服务器之TOP命令详解
    在做性能调优的过程中,我们经常需要用到top命令来查看服务器实时的资源占用情况,通过top命令,我们可以查看到服务器的各项性能指标以及各个进行的资源使用情况。命令格式:top[参数]命令参数:-b批处理-c显示完整的治命令-I忽略失效过程-s保密模式-S累积模式-i<......
  • Nginx中的limit_req模块和limit_conn模块详解
    引言在高流量场景下,良好的限流和连接控制策略至关重要,以防止服务器过载,确保服务稳定性和高可用性。Nginx提供了limit_req和limit_conn模块,用以实现请求频率和并发连接数的限制。本文将详细介绍这两个模块的生效阶段和生效范围,并提供实际配置示例,解释相关指令的作用。limit_re......
  • 大二必做项目贪吃蛇超详解之下篇游戏核心逻辑实现
    贪吃蛇系列文章上篇win32库介绍中篇设计与分析下篇游戏主逻辑可以在Gitee上获取贪吃蛇代码。文章目录贪吃蛇系列文章5.核心逻辑实现分析5.3GameRun5.3.1PrintScore5.3.2CheckVK5.3.3BuyNewNode5.3.4NextIsFood5.3.4EatFood5.3.5NotFood5.3.6C......