嵌入式之C/C++(二)内存

内存管理是 C/C++ 编程的核心,尤其是嵌入式开发中,对内存的精准把控直接影响程序的稳定性和性能。本文系统梳理 C/C++ 内存分配方式、栈 / 堆区别、函数参数压栈、内存泄漏等高频考点,结合嵌入式开发场景,帮你彻底吃透内存管理的底层逻辑。

1 C 语言中的内存分配方式?

C 语言的内存分配主要分为静态存储区、栈、堆三种核心方式,对应程序运行时不同的内存区域:

①静态存储区分配

  • 分配时机:程序编译阶段完成分配,无需运行时操作;
  • 生命周期:贯穿程序整个运行期间,程序退出时由系统释放;
  • 存储内容:全局变量、static 静态变量(局部 / 全局)、常量字符串;
  • 特点:内存地址固定,无需手动管理,易造成内存长期占用。
cs 复制代码
// 全局变量(静态存储区)
int global_var = 10;
// 静态变量(静态存储区)
static int static_var = 20;

void func() {
    // 局部静态变量(仍在静态存储区,仅初始化一次)
    static int local_static = 30;
    local_static++;
    printf("local_static = %d\n", local_static);
}

int main() {
    func(); // 输出31
    func(); // 输出32(值保留,未重新初始化)
    return 0;
}

②栈上分配

  • 分配时机:函数执行时自动分配,函数结束时自动释放;
  • 存储内容:函数参数、局部变量、函数返回地址、寄存器上下文;
  • 特点:速度快、内存连续、空间有限(Windows 默认 2M,Linux 默认 8M),无需手动管理。

③堆上分配

  • 分配时机 :运行时通过malloc/calloc/realloc(C)或new(C++)手动申请;
  • 释放时机 :需通过free(C)或delete(C++)手动释放,否则导致内存泄漏;
  • 存储内容:动态创建的变量 / 对象(如动态数组、链表节点);
  • 特点:空间灵活(受限于虚拟内存)、内存不连续、易产生碎片、分配 / 释放速度慢。
cs 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 堆上分配4个int大小的内存
    int *arr = (int*)malloc(4 * sizeof(int));
    if (arr == NULL) { // 堆分配失败会返回NULL,必须检查
        perror("malloc failed");
        return 1;
    }
    // 使用堆内存
    for (int i = 0; i < 4; i++) {
        arr[i] = i + 1;
    }
    // 释放堆内存
    free(arr);
    arr = NULL; // 避免野指针
    return 0;
}

2 栈与堆的核心区别?

| 特性 | 栈(Stack) | 堆(Heap) |
| 申请 / 释放 | 操作系统自动分配 / 释放 | 程序员手动malloc/new申请、free/delete释放 |
| 空间大小 | 有限(MB 级,系统固定) | 灵活(GB 级,受虚拟内存限制) |
| 内存布局 | 向低地址扩展,连续内存 | 向高地址扩展,非连续内存(链表管理空闲地址) |
| 分配效率 | 极快(系统底层操作) | 较慢(需遍历空闲链表,可能产生碎片) |
| 生命周期 | 函数执行期 | 直到手动释放或程序退出 |

安全性 自动释放,不易泄漏 易泄漏(忘记 free/delete)、易产生野指针

3 栈在C/C++中的核心作用?

栈是程序运行的基础,尤其是嵌入式和多线程开发中,其作用不可替代:

①函数运行的核心载体

  • 存储函数参数、局部变量、返回地址;
  • 保存函数调用时的寄存器上下文,函数返回时恢复现场;
  • 维系函数调用链(如A调用B,B调用C,栈记录各函数的返回地址)。

②多线程 / 中断的基石

  • 每个线程拥有独立的栈空间,避免线程间数据冲突;
  • 嵌入式系统中,中断/异常处理有专属栈,保证中断响应的独立性;
  • 操作系统通过栈管理线程的运行状态,是多任务调度的核心基础。

4 C 语言函数参数的压栈顺序?

①核心规则:从右至左入栈

cs 复制代码
#include <stdio.h>

void func(int a, int b, int c) {
    // 栈地址:栈底(高地址)→ 低地址(栈顶)
    // 入栈顺序:c → b → a,因此a的地址最小(栈顶)
    printf("&a = %p\n", &a);
    printf("&b = %p\n", &b);
    printf("&c = %p\n", &c);
}

int main() {
    func(1, 2, 3);
    return 0;
}

输出结果

复制代码
&a = 0x7ffeefbff5c8
&b = 0x7ffeefbff5cc
&c = 0x7ffeefbff5d0

(地址值递增,说明 c 先入栈,a 最后入栈)

②为什么选择从右至左?

核心目的:支持可变参数(如printf)

  • 从右至左入栈时,最左边的参数(如printf的格式串)位于栈顶,通过栈指针可直接定位;
  • 若从左至右入栈,可变参数的个数无法确定,无法通过栈指针计算参数位置。

5 C++函数返回值的处理方式?

C++支持多种返回方式,不同方式适用于不同场景:

返回方式 特点 使用场景
按值返回 拷贝返回值到临时变量,开销较大 基本数据类型(int/char)、小型结构体
按常量引用返回 不拷贝,效率高,禁止修改返回值 大型对象(如 string、自定义类)
按地址返回 直接返回内存地址,风险高 慎用(避免返回局部变量地址)

注意:禁止返回栈上局部变量的引用/地址(函数结束后栈内存释放,引用/地址变为野指针)。

cs 复制代码
// 错误示例
int& bad_func() {
    int a = 10; // 栈变量
    return a; // 返回栈变量引用,函数结束后a被释放
}

// 正确示例
const int& good_func(const int& val) {
    static int b = val; // 静态存储区,生命周期长
    return b;
}

6 C++ 拷贝构造函数的参数传递规则?

核心结论:不能按值传递,必须按引用传递

原因:避免无限递归

  • 若拷贝构造函数参数为值传递,调用时需先将实参拷贝给形参,这又会触发拷贝构造函数;
  • 循环调用会导致栈溢出,无法完成拷贝。

正确示例

cpp 复制代码
#include <iostream>
using namespace std;

class Example {
public:
    int aa;
    // 拷贝构造函数:引用传递
    Example(int a) : aa(a) {}
    Example(const Example& ex) { // const保证不修改原对象
        aa = ex.aa;
        cout << "拷贝构造函数调用" << endl;
    }
};

int main() {
    Example e1(10);
    Example e2 = e1; // 调用拷贝构造函数
    cout << "e2.aa = " << e2.aa << endl;
    return 0;
}

7 C++完整的内存布局?

C++虚拟内存分为6个核心区域,比C语言更细化:

| 内存区域 | 存储内容 | 生命周期 |
| 代码段 | 字符串常量(只读)、机器指令 | 程序整个运行期 |
| 数据段 | 已初始化的全局变量、静态变量 | 程序整个运行期 |
| BSS 段 | 未初始化 / 初始化为 0 的全局变量、静态变量 | 程序整个运行期(运行时初始化为 0) |
| 堆区 | 动态分配的内存(new/malloc) | 手动释放前 |
| 映射区 | 动态链接库、文件映射(mmap) | 程序运行期 |

栈区 函数参数、局部变量、返回地址 函数执行期

8 内存泄漏:定义、检测与避免?

8.1 内存泄漏的定义?

申请的堆内存使用完毕后未释放,且无任何指针指向该内存,导致这块内存无法被回收,程序运行时间越长,占用内存越多,最终可能导致系统崩溃。

8.2 内存泄漏的判断方法?

  • 编码阶段预防 :配对使用malloc/free/new/delete,在内存分配后立即规划释放逻辑;
  • 手动管理:维护内存链表,记录所有堆内存的申请/释放,程序结束时检查链表是否为空;
  • 工具检测
    • 通用工具:Valgrind(Linux)、AddressSanitizer(Clang/GCC);
    • 专用工具:ccmalloc、Dmalloc、Leaky(嵌入式场景);
    • C++ 智能指针:std::unique_ptr/std::shared_ptr(自动释放内存,避免泄漏)。

8.3 嵌入式场景避坑要点?

  • 嵌入式系统内存资源有限,即使小内存泄漏也会导致系统崩溃;
  • 中断 / 回调函数中避免动态分配内存(易泄漏且影响实时性);
  • 自定义内存池管理堆内存,减少碎片和泄漏风险。
相关推荐
浅念-2 小时前
C语言——内存函数
c语言·经验分享·笔记·学习·算法
●VON2 小时前
Flutter for OpenHarmony:基于 SharedPreferences 的本地化笔记应用架构与实现
笔记·学习·flutter·ui·架构·openharmony·von
程序员清洒2 小时前
Flutter for OpenHarmony:Dialog 与 BottomSheet — 弹出式交互
开发语言·flutter·华为·交互·鸿蒙
cyforkk2 小时前
07、Java 基础硬核复习:面向对象编程(进阶)的核心逻辑与面试考点
java·开发语言·面试
求真求知的糖葫芦2 小时前
耦合传输线分析学习笔记(九)对称耦合微带线S参数矩阵推导与应用(下)
笔记·学习·矩阵·射频工程
钱多多先森2 小时前
【Dify】使用 python 调用 Dify 的 API 服务,查看“知识检索”返回内容,用于前端溯源展示
开发语言·前端·python·dify
qq_417129252 小时前
基于C++的区块链实现
开发语言·c++·算法
霍理迪2 小时前
JS—数组
开发语言·前端·javascript
Ulyanov2 小时前
超越平面:用impress.js打造智能多面棱柱演示器
开发语言·前端·javascript·平面