单片机/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~行数-1,j=列数-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; ,避免悬空指针。


相关推荐
Jason_zhao_MR3 小时前
Linux与实时控制如何兼得
linux·嵌入式硬件·机器人·工业控制
BSD_CGQ4 小时前
FSR传感器STM32 DMA高速采样工程实现(高精度、低CPU占用)
stm32·单片机·嵌入式硬件·压力传感器·源头工厂·柔性薄膜压力传感器·可变电阻压力传感器
Escalating_xu4 小时前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
芯岭技术郦7 小时前
32 位 ARM® Cortex®-M0+ 内核MCU普冉MS32C001-C
单片机·嵌入式硬件
骑着毛驴数星星7 小时前
STM32 CAN波特率与采样点计算,基于HAL库
stm32·单片机·嵌入式硬件
zhangrelay8 小时前
Arduino-MEGA-ESP单片机理论课笔记02-硬件-2026
笔记·单片机·嵌入式硬件
艾芯微科技8 小时前
钰泰 ETA5055V330DD1E|SOT-223 3.3V LDO,低压差、大电流线性稳压选型分享
网络·单片机·嵌入式硬件·集成测试·51单片机
新晨单片机设计9 小时前
S023A-基于STM32单片机智能插座【Proteus仿真+Keil程序+原理图】
stm32·单片机·proteus·单片机智能插座
威嵌神州10 小时前
恩智浦 T1022 性能怎么样?QorIQ 架构中端工业嵌入式优势解析
嵌入式硬件
weixin_6906547410 小时前
龙迅#LT9611EX 现MIPI转HDMI1.4功能主推型号,分辨率高达4K30HZ,IC内置I2C无需外部12C控制。
嵌入式硬件·音视频·信号处理