当前位置: 首页 > article >正文

【C语言】(20)动态内存分配

动态内存分配是通过stdlib标准库函数来管理的,主要包括malloccallocreallocfree。这些函数允许在程序运行时分配和释放内存,使得内存的使用更加灵活。

1.动态内存分配函数

1.1 malloc

malloc函数用于分配一定数量的内存。它的原型在stdlib.h头文件中定义:

void* malloc(size_t size);
  • size:需要分配的内存字节数。
  • 返回值:成功时返回指向分配内存的指针;如果分配失败,返回NULL

示例

#include <stdlib.h>

int main() {
    int *p = malloc(10 * sizeof(int)); // 分配10个整数的空间
    if (p == NULL) {
        // 处理内存分配失败的情况
    }
    // 使用p...
    free(p);
    return 0;
}

1.2 calloc

calloc函数用于分配并初始化内存。它的原型也在stdlib.h头文件中定义:

void* calloc(size_t num, size_t size);
  • num:需要分配的元素数量。
  • size:每个元素的大小。
  • 返回值:成功时返回指向分配并初始化为0的内存的指针;如果分配失败,返回NULL

示例

#include <stdlib.h>

int main() {
    int *p = calloc(10, sizeof(int)); // 分配并初始化10个整数
    if (p == NULL) {
        // 处理内存分配失败的情况
    }
    // 使用p...
    free(p);
    return 0;
}

1.3 realloc

realloc函数用于重新分配内存。它可以增加或减少已分配内存的大小。其原型定义在stdlib.h头文件中:

void* realloc(void* ptr, size_t newSize);
  • ptr:指向先前分配的内存的指针。
  • newSize:新的内存大小。
  • 返回值:成功时返回指向新分配内存的指针;如果分配失败,返回NULL,原指针ptr仍然有效。

示例

#include <stdlib.h>

int main() {
    int *p = malloc(10 * sizeof(int)); // 最初分配10个整数的空间
    p = realloc(p, 20 * sizeof(int)); // 现在重新分配为20个整数的空间
    if (p == NULL) {
        // 处理内存分配失败的情况
    }
    // 使用p...
    free(p);
    return 0;
}

1.4 free

free函数用于释放先前通过malloccallocrealloc分配的内存。它的原型定义在stdlib.h头文件中:

void free(void* ptr);
  • ptr:指向需要释放的内存的指针。

注意:一旦内存被释放,指针ptr就不应再被访问。为了避免悬挂指针,建议将ptr设置为NULL

示例

#include <stdlib.h>

int main() {
    int *p = malloc(10 * sizeof(int));
    // 使用p...
    free(p);
    p = NULL; // 避免悬挂指针
    return 0;
}

2.动态内存分配的常见错误

2.1 未检查返回值

使用malloccalloc分配内存时,如果系统没有足够的内存可供分配,这些函数将返回NULL。不检查这些函数的返回值直接使用返回的指针,可能会导致程序解引用空指针而崩溃。

错误示例

int *ptr = malloc(sizeof(int) * 50); // 假设分配失败
*ptr = 5; // 如果ptr为NULL,这里会导致程序崩溃

正确做法

int *ptr = malloc(sizeof(int) * 50);
if (ptr == NULL) {
    // 处理内存分配失败的情况
}

2.2 忘记释放内存

每次调用malloccallocrealloc分配的内存,在不再需要时应该使用free函数释放。忘记释放内存会导致内存泄露,可能导致程序随着时间的推移而运行缓慢,并最终耗尽可用内存。

2.3 重复释放内存

对同一块内存调用free两次是未定义行为,可能会导致程序崩溃。

错误示例

int *ptr = malloc(sizeof(int));
free(ptr);
free(ptr); // 重复释放

2.4 使用已释放的内存

释放内存后继续使用该内存的指针,会导致未定义行为,通常称为悬挂指针(dangling pointer)问题。

错误示例

int *ptr = malloc(sizeof(int));
free(ptr);
*ptr = 10; // 使用已释放的内存

2.5 越界访问

动态分配的内存块有固定的大小,超出其边界的访问是未定义的行为,可能会导致数据损坏或程序崩溃。

2.6 错误的内存大小计算

在使用malloc等函数时,传递错误的大小给这些函数,可能会导致内存分配不足或过多,从而引起未定义行为或浪费资源。

2.7 忘记为realloc返回的新指针赋值

realloc函数用于调整已分配内存的大小。如果realloc成功,它会返回指向新内存的指针,可能与原来的指针不同。如果不更新指针,可能会导致访问旧指针和内存泄露。

错误示例

int *ptr = malloc(sizeof(int) * 2);
realloc(ptr, sizeof(int) * 4); // 忽略了realloc的返回值

正确做法

int *ptr = malloc(sizeof(int) * 2);
int *new_ptr = realloc(ptr, sizeof(int) * 4);
if (new_ptr != NULL) {
    ptr = new_ptr;
}

3.柔性数组

柔性数组成员(Flexible Array Member,FAM)提供了一种方便的方式来表示结构体末尾的可变长度数组。然而,柔性数组本身并不支持动态扩容,因为它们的大小在结构体实例被首次分配内存时就已经确定。要实现类似“动态扩容”的功能,你需要手动重新分配内存,并小心处理数据的复制和迁移。

基本思路

要“动态扩容”一个包含柔性数组的结构体,可以按照以下步骤操作:

  1. 确定新的大小:根据需要扩容的数量,计算新的总大小。
  2. 重新分配内存:使用realloc函数为原结构体实例分配更大的内存块。
  3. 检查分配结果:确保realloc成功,否则处理内存分配失败的情况。
  4. 更新结构体状态:如果扩容成功,更新结构体内部的状态,如元素计数。

注意

  • 使用realloc进行内存重新分配时,如果新的内存分配成功,原内存块的内容(直到较小的旧尺寸或新尺寸)会被自动复制到新内存块中,原内存块随后会被释放。
  • 如果realloc返回NULL,原内存块不会被释放。因此,在处理realloc失败时要小心,以避免内存泄漏。
  • 在实际使用中,应考虑如何处理数据迁移和默认值初始化等问题,尤其是扩容后新添加的元素。

示例

假设我们有以下包含柔性数组的结构体定义:

struct flex_array_struct {
    size_t count;
    int data[]; // 柔性数组成员
};

接下来,我们将通过一个函数来扩展这个结构体实例的data数组大小:

#include <stdio.h>
#include <stdlib.h>

struct flex_array_struct* resize_flex_array(struct flex_array_struct* array, size_t new_count) {
    // 计算新的内存大小
    size_t new_size = sizeof(struct flex_array_struct) + sizeof(int) * new_count;
    
    // 尝试重新分配内存
    struct flex_array_struct* new_array = realloc(array, new_size);
    if (new_array == NULL) {
        // 内存分配失败,根据需要处理错误
        return NULL;
    }
    
    // 更新新数组的元素计数
    new_array->count = new_count;
    return new_array;
}

int main() {
    // 初始化并分配一个具有5个元素的柔性数组
    size_t initial_count = 5;
    struct flex_array_struct* my_array = malloc(sizeof(struct flex_array_struct) + sizeof(int) * initial_count);
    if (my_array == NULL) {
        // 处理内存分配失败
        return 1;
    }
    my_array->count = initial_count;
    
    // 动态扩容数组到10个元素
    size_t new_count = 10;
    my_array = resize_flex_array(my_array, new_count);
    if (my_array == NULL) {
        // 处理内存分配失败
        free(my_array);
        return 1;
    }
    
    // 使用扩容后的数组...
    
    // 释放内存
    free(my_array);
    return 0;
}

http://www.kler.cn/news/234641.html

相关文章:

  • HiveQL——不借助任何外表,产生连续数值
  • Linux CentOS stream 9 alias
  • 【JavaScript 漫游】【014】正则表达式通关
  • VitePress-14- 配置-titleTemplate 的作用详解
  • 2.11学习总结
  • Redisson分布式锁 原理 + 运用 记录
  • CentOS基于volatility2的内存取证实验
  • bcdedit /store 填什么,Windows11的BCD文件在哪里?
  • CrossOver虚拟机软件功能相似的软件
  • 6.JavaScript中赋值运算符,自增运算符,比较运算符,逻辑运算符
  • 深入理解 Nginx 插件及功能优化指南
  • 绕过安全狗优化
  • 力扣_字符串5—解码方法
  • 吹响AI PC号角!微软在Windows中不断增加“Copilot含量”
  • 【Spring学习】Spring Data Redis:RedisTemplate、Repository、Cache注解
  • Java字符串(包含字母和数字)通用排序
  • 【MySQL】-15 MySQL综合-1(数据库概念+数据库涉及技术)
  • 【数据结构】13:表达式转换(中缀表达式转成后缀表达式)
  • 【Java】悲观锁和乐观锁有什么区别?
  • 【java】笔记10:类与对象——本章练习
  • Leetcode 3033. Modify the Matrix
  • Spring + Tomcat项目中nacos配置中文乱码问题解决
  • 代码随想录算法训练营第39天(动态规划02● 62.不同路径 ● 63. 不同路径 II
  • 第二节 zookeeper基础应用与实战
  • 知识价值2-什么是IDE?新手用哪个IDE比较好?
  • python:lxml 读目录.txt文件,用 xmltodict 转换为json数据,生成jstree所需的文件
  • 寒假作业5
  • 基于python和matlab的复杂函数拟合的方法、工具以及学习资料
  • 【中间件学习】什么是中间件
  • 【Linux进程间通信】用管道实现简单的进程池、命名管道