单片机/C语言八股:(十二)C 语言中 malloc 和 free 用法(动态分配内存)

上一篇 下一篇
指针的补充,包括指针的类型和大小

目 录


malloc 和 free 用法(动态分配内存)

定义一个长度的变量时,一般就不需要用 malloc ,除非有动态分配内存的硬性要求。一般都是用 malloc 分配一个数组的内存空间。

💥 需要引用 #include <stdlib.h> 头文件(standard library 的缩写) 💥

0)为什么要用 malloc

这就涉及到 动态内存分配 了,动态分配的内存都位于堆上:

  1. 栈内存(局部变量) 在函数返回后自动销毁,不能用于返回数组。
  2. 全局变量 / static 变量虽生命周期长,但有严重局限
    • 不可重入:多次调用函数会互相覆盖结果(所有调用共享同一块内存);
    • 线程不安全:多线程环境下会导致数据竞争;
  3. 堆内存malloc 分配)由程序员控制,可在运行时动态申请任意大小的内存,并跨函数使用;
  4. 只有 malloc 能保证
    • 每次调用分配独立内存
    • 返回的指针可被调用者安全使用并 free
    • 支持运行时才确定大小的数据结构(如动态数组、链表等)。

1)malloc(),分配内存

申请的内存可用于各种类型数据

malloc:memory allocate.

功能: 在程序运行时(而不是编译时)向操作系统申请一块指定大小的连续内存空间(位于堆上),并返回指向这块内存起始地址的指针

函数原型(助了解):

c 复制代码
void malloc(size_t _Size)
  • 返回值:返回一个 void* 指针,指向新分配的内存块的首地址

    • 函数原型的返回值类型是 void* ,这意味着我们在调用 malloc 的时候,可以将其强制转换成任意类型去使用。
    • 失败(如内存不足):返回 NULL ,一般调用之后判断一下结果是否为 NULL 。
  • 参数:要申请的字节数。

基础语法(√):

c 复制代码
/* 原始写法 */
元素类型* 指针名 = (元素类型*)malloc(总字节数);  // 这种写法需要自己根据所用系统的规则,计算总字节数,比较生硬,易出错

/* 高效写法 */
元素类型* 指针名 = (元素类型*)malloc(sizeof(元素类型)*个数);  //sizeof(元素类型)*个数 = 总字节数, sizeof(元素类型): 获取元素类型所占字节数

在申请内存之后,要判断一下 malloc 分配内存是否成功:

c 复制代码
if (指针名==NULL)
{
    printf("malloc failed\n");
    return -1;
}

如果不成功,main() 函数就直接返回 -1(返回啥不重要),中断函数。

1.0)malloc 分配的内存是未初始化的

malloc 分配的内存内容是未初始化的 (里面是垃圾值)。如果需要初始化为 0,可以使用 calloc ,也可以挨个赋值。

也可以使用 memset() 函数,要引入 #include <string.h> 头文件:

c 复制代码
#include <string.h>

int *presult = malloc(m * p * sizeof(int));
memset(presult, 0, m * p * sizeof(int));  // 参数含义依次为:首地址,用什么值覆盖,覆盖字节数是多少

1.1)分配的内存用于数组

① 用于一维数组

申请内存,示例:

  • 申请 10 个字符数组空间:char* pchar = (char*)malloc(sizeof(char)*10);
  • 申请 10 个整数数组空间:int* pint = (int*)malloc(sizeof(int)*10);
  • 原始写法,以 64 位系统为例(一般不用):
    • 申请 10 个整数数组空间:int* pint = (int*)malloc(40);
    • 申请 10 个 double 类型数组空间:double* pdouble = (double*)malloc(80);

那申请之后,该如何使用/遍历呢?

首先我们要知道三点:①指针也是有类型的;②指针也是可以做指针运算(+/-/++/--)的;③数组名就是指向数组首地址的常量指针。具体使用可参考博客:C++ 学习与 CLion 使用:(十)★指针★,......指针运算、动态内存分配、数组元素的移除与插入...... ,看链接中的 5),C 和 C++ 的指针操作是一样的。

不使用 malloc 的时候,我们比如说是定义一个数组 int a[10]; ,然后后续可以直接用 a[0]、a[1] 表示数组元素。

其实 int* pint = (int*)malloc(sizeof(int)*10); 就相当于:

c 复制代码
int a[10];
int *pa = a;  // int *pa = &a[0];

pint 就等同于 a ,区别是后者被分配的内存位于栈上,而 malloc 分配的内存位于堆上。

我们仍以 int* pint = (int*)malloc(sizeof(int)*10); 为例:

pint 可以直接当数组名使用,即: pint[0]、pint[1]、......、pint[9]pint[i] 中的 i=0~元素个数-1但索引不能越界 !

② 用于二维数组

申请用于二维数组的内存,代码会复杂一些,语法:

c 复制代码
/* 高效写法 */
数组类型 (*指针名)[列数] = (数组类型*)malloc(sizeof(数组类型)*行数*列数);  // sizeof(数组类型)*行数*列数 = 总字节数

示例:

  • 申请一个 2×3 的二维字符数组空间:char (*pchar)[3] = (char*)malloc(sizeof(char)*2*3);
  • 申请一个 2×3 的二维整数数组空间:int (*pint)[3] = (int*)malloc(sizeof(int)*2*3);

那申请之后,该如何使用/遍历呢?

和一维数组的基本一致,我们以 int (*pint)[3] = (int*)malloc(sizeof(int)*2*3); 为例:

pint 可以直接当数组名使用,即: pint[0][0]、pint[0][1]、......、pint[1][2]pint[i][j] 中的 i=0~行数-1j=列数-1但索引不能越界 !

③ 完整示例
c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    /* 分配用于长度为3的一维整型数组的空间 */
    int *parry1 = (int*)malloc(sizeof(int)*3);
    if (parry1 == NULL) {
        printf("malloc failed\n");
        return -1;
    }
    // 赋值(加个类型转换, 加强一下代码能力)
    for (char i=0; i<3; i++)
    {
        parry1[i]=(int)i;
    }
    printf("parry1 address: %p\n", parry1);
    printf("parry1 elements: %d %d %d\n", parry1[0],parry1[1],parry1[2]);

    /* 释放内存 */
    free(parry1);
    printf("----------------------------------\n");

    /* 分配用于长度为2×3的二维整型数组的空间 */
    int (*parry2)[3] = (int*)malloc(sizeof(int)*2*3);
    if (parry2 == NULL) {
        printf("malloc failed\n");
        return -1;
    }
    // 赋值(加个类型转换, 加强一下代码能力)
    for (char i=0; i<2; i++)
    {
        for (char j=0; j<3; j++)
            parry2[i][j]=(int)i+(int)j;
    }
    printf("parry2 address: %p\n", parry2);
    printf("parry1 elements: %d %d %d ... %d \n", parry2[0][0],parry2[0][1],parry2[0][2],parry2[1][2]);

    free(parry2);

    return 0;
}

运行结果为:

c 复制代码
parry1 address: 000001b8c2f21440
parry1 elements: 0 1 2
----------------------------------
parry2 address: 000001b8c2f21440
parry1 elements: 0 1 2 ... 3

1.2)分配的内存用于结构体/结构体数组

在 C 语言中,结构体的完整类型名是 struct 结构体名 ,而不是单单的 结构体名 .

基础语法(√):

c 复制代码
/* 先定义一个结构体类型 */
struct 结构体名 
{
    结构体成员1;
    结构体成员2;
    ...
};

/* 高效写法 */
struct 结构体名* 指针名 = (struct 结构体名*)malloc(sizeof(struct 结构体名));  // 分配一个结构体实例

/* 扩展:分配 n 个结构体(结构体数组) */
struct 结构体名* 指针名 = (struct 结构体名*)malloc(sizeof(struct 结构体名) * n);  // 相当于动态创建 struct 结构体名 数组[n]

分配+使用步骤总结:

  1. 先定义结构体类型(如 struct Student);
  2. malloc + sizeof(struct 类型) 分配内存;
  3. 检查返回值是否为 NULL(防内存分配失败);
  4. 通过 ->[i].成员 访问成员(和数组很像,直接把指针名当结构体名使用);
  5. 使用完毕调用 free(指针名) 释放内存;
  6. (可选但推荐)将指针置为 NULL,避免悬空指针。

完整示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* 先定义结构体类型:学生 */
struct Student {
    int id;
    char name[32];
};

int main() {
    // 分配 2 个学生的内存
    struct Student* students = (struct Student*)malloc(sizeof(struct Student) * 2);
    
    if (students==NULL) 
    {
        printf("malloc failed\n");
    	return -1;
    }

    // 赋值
    students[0].id = 1;
    strcpy(students[0].name, "王五");

    students[1].id = 2;
    strcpy(students[1].name, "赵六");

    // 读取并打印
    for (int i = 0; i < 2; i++) {
        printf("学生 %d: ID=%d, 姓名=%s\n", i, students[i].id, students[i].name);
    }

    // 释放
    free(students);
    students = NULL;

    return 0;
}

运行结果如下:

c 复制代码
学生 0: ID=1, 姓名=王五
学生 1: ID=2, 姓名=赵六

1.3)分配的内存用于字符串

基础语法(√):

c 复制代码
char *指针名 = (char*)malloc(sizeof(char)*字符个数)
/* 由于char就占一个字节, 所以用原始写法更简单, 但为了代码的统一性,也可以用高效写法 */
char *指针名 = (char*)malloc(字符个数)

分配完内存之后,使用 strcpy 函数给字符串赋值(引用 #include <string.h> 头文件)

c 复制代码
strcpy(指针名, "字符串内容");

完整示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    char *s = (char*)malloc(10);
    strcpy(s, "Hello");
    printf("%s\n", s);
    free(s);
    return 0;
}

给其他的类型分配内存大差不差......

2)free(),释放内存

函数原型:

c 复制代码
void free(void* _Block);
  • 参数:malloc 申请的内存的 首地址

函数使用:

c 复制代码
/* malloc 分配内存, 高效写法 */
元素类型* 指针名 = (元素类型*)malloc(sizeof(元素类型)*个数);  //sizeof(元素类型)*个数 = 总字节数

/* 使用 free 函数释放内存 */
free(指针名);  // 这个指针名就是首地址

注意:

  • malloc 后,如果这块内存要做的事情做完了,但是没有 free ,操作系统就不敢乱动,这块空间就没法做其他事,久而久之,可用的内存就会变少,这就是 内存泄漏(内存丢失)
  • 如果 malloc 后,事情还没做呢,就将指针又 malloc 到其他内存了,那么原来 malloc 的内存就荒废了,也会造成内存泄漏(内存丢失);
  • 错误 free(如 free 栈/全局变量),会导致 程序崩溃
  • 不能重复 free ,已经释放过的内存,操作系统(电脑)可能会分配给其他程序了,如果再 free 的话,会导致电脑紊乱。

free 之后,建议令 指针名 = NULL; ,避免悬空指针。


相关推荐
一叶落4382 小时前
LeetCode 136. 只出现一次的数字(C语言详解 | 哈希表 + 排序 + 位运算)
c语言·数据结构·算法·leetcode·哈希算法·散列表
国产电子元器件2 小时前
超充时代来了,大电流检测技术面临什么挑战?
嵌入式硬件
古译汉书2 小时前
【数据结构算法】二分查找
c语言·开发语言·数据结构·c++·算法
小龙报2 小时前
【算法通关指南:算法基础篇】二分答案专题:1.木材加工 2.砍树
c语言·数据结构·c++·算法·启发式算法
炸膛坦客2 小时前
单片机/C语言八股:(十一)指针的补充,包括指针的类型和大小
c语言·开发语言·单片机
故以往之不谏2 小时前
算法专题--数组二分查找--Leetcode704题
c语言·开发语言·c++·算法·新人首发
努力中的编程者2 小时前
栈和队列(C语言底层实现栈)
c语言·开发语言·数据结构·c++
Saniffer_SH2 小时前
【每日一题】PCIe链路协商的时候进入Polling compliance如何排错?
服务器·人工智能·驱动开发·嵌入式硬件·测试工具·fpga开发·自动化
浩子智控3 小时前
航天高可靠性设备开发—抗辐射
嵌入式硬件·fpga开发·硬件工程