目录
一,C语言内存模型基本结构
- C 语言内存模型,是程序运行时内存分配和管理的核心,它定义了程序如何使用内存资源
- C 语言内存模型主要分为几个区域,每个区域有不同的特性和用途
1,堆与栈的区别
C 语言中,内存分为栈区,堆区,全局区(也叫静态区)
栈区:临时储物柜(自动开关)
用于存储函数内部的局部变量,比如 int a = 10;
栈的特点是自动管理,变量的生命周期和函数的调用紧密相关。
函数调用时,栈会为局部变量分配空间;函数结束时,栈上的变量会自动销毁,空间被回收
这种自动管理机制,使得栈非常高效,但也限制了变量的生命周期
void func() {
int local; // 在栈区,函数结束时自动销毁
}
栈的生长方向,高地址 --> 低地址,这意味着新分配的变量会占用更低的内存地址
堆区:自选仓库(手动管理)
用于存储动态分配的内存,通过 malloc() 手动申请内存空间
比如 int *p = malloc(100);
和栈不同,堆上的内存不会随着函数结束而自动释放,必须由程序员通过 free() 函数手动归还
如果不释放,可能导致内存泄漏
堆的生长方向通常是低地址向高地址生长,和栈相反
全局区:固定货架
用于存储函数外部定义的全局变量和静态变量
比如 int global;
或函数内的 static int s;
这些变量在程序启动时分配空间,直到程序结束才被销毁
全局区的内存分配是固定的,不会随函数调用变化
堆与栈生长方向相反的原因:
为了最大化内存空间的利用率
比如一个仓库,栈就像一个工人从仓库顶层(高地址)开始往下摆箱子(变量)
每次调用函数就在更低地址分配空间
而堆就像另一个人从仓库底部(低地址)开始往上堆箱子(动态内存)
每次分配内存就占用更高的地址
这样设计确保不会“撞车”,从而提高内存利用效率
栈的自动分配和释放机制使其适合存储临时数据,而堆的手动管理则适合需要长期存在的动态数据
2,全局变量与局部变量
变量的存储位置和生命周期,是理解 C 语言内存模型的重要部分
不同类型的变量存储在不同的内存区域
| 变量类型 | 存储位置 | 生命周期 |
| 函数外变量 | 全局区 | 程序结束才消失 |
| malloc 分配 | 堆区 | 直到调用 free |
| 字符串常量 | 只读区 | 程序结束才消失 |
- 全局变量:定义在函数外部,存储在全局区,生命贯穿整个程序运行
- 局部变量:定义在函数内部,存储在栈区,生命周期局限于函数调用期间
- 字符串常量:
- char *s1 = "hello",存储在只读区
- char s2[] = "world",将字符串复制到栈区,可以修改
char *s1 = "hello"; // 只读区(不可修改)
char s2[] = "world"; // 栈区(可修改)
问:堆 / 栈为什么生长方向相反?
答:比如两个工人搬货,栈工人从仓库顶层往下摆好箱子,也就是高地址 --> 低地址
;堆工人从仓库底开始,往上放箱子,低地址 --> 高地址
;不会撞车,空间利用率高
;区别在于,栈是自动的,而堆是手动分配的
栈空间:自动清理;速度快,只需要移动指针;大小 MB 级别
堆空间:手动申请 / 释放;较慢,要搜索空闲空间;大小 GB 级别
内存泄漏
// 栈使用(安全自动)
void safe_func() {
int nums[100]; // 栈分配
} // 自动释放
// 堆使用
void risky_func() {
int* nums = malloc(100 * sizeof(int)); // 堆分配
if (nums == NULL) exit 1; // 需要检查
// nums 的调用
free(nums); // 需要释放
nums = NULL; // 需要置空
}
3,内存对齐与填充
C 语言中,内存对齐影响数据的存储方式和访问效率
内存对齐指的是数据在内存中的地址必须是某个特定值的倍数
(比如 2,4,8 的倍数)
以便 CPU 能够更高效地访问数据
- 为什么需要内存对齐呢?
- CPU 在读取内存数据时,通常以字(word,4 字节或 8 字节)为单位进行操作
- 如果数据没有对齐,CPU 需要多次读取内存并进行拼接操作,就会降低性能
- 所以,编译器会自动调整变量的存储位置,使其地址符合对齐要求
- 内存填充(Padding)
- 为了满足内存对齐的要求,编译器会在结构体中插入额外的字节(填充字节)
- 确保每个成员的地址符合对齐规则
struct Example {
char a; // 1 字节
int b; // 4 字节,需要对齐到 4 字节边界
};
这个结构体中,char a 占用 1 字节,但为了让 int b 的地址对齐到 4 字节边界
编译器会在 a 之后插入 3 个填充字节
所以,整个结构体的大小是 8 字节
- 对齐的影响
- 内存对齐虽然会增加内存使用量,但可以显著提高 CPU 访问数据的效率
- 在设计结构体时,程序员可以通过调整成员的顺序(大的类型在前)
- 以便减少填充的字节,从而优化内存使用
二,C 语言内存管理机制
C 语言不像一些高级语言(如 Java),有自动垃圾回收机制,它的内存分配释放完全由程序员手动控制
以下介绍 内存分配函数,内存泄漏,内存管理三个方面
1,内存分配函数
malloc(), calloc(), realloc() 和 free()
这些函数允许程序员,在堆区动态分配和释放内存
- malloc():分配指定大小的内存空间,返回一个指向该空间的指针
- malloc() 分配的内存是未初始化的,可能包含垃圾值
int *p = (int *)malloc(100 * sizeof(int));
if (p != NULL) {
// 分配失败逻辑
exit(1);
}
- calloc():类似 malloc(),但会将分配的内存初始化为 0
- calloc() 接受两个参数,元素个数 + 每个元素的大小
int *p = (int *)calloc(100, sizeof(int));
- realloc():用于调整已分配内存的大小,可能返回一个新的指针
- realloc() 可能会移动内存块,因此返回的指针可能与原来的不同
int *new_p = (int *)realloc(p, 200 * sizeof(int));
if (new_p == NULL) {
// 失败逻辑
free(p);
exit(1);
}
p = new_p;
- free():释放动态分配的内存,必须与 malloc(), calloc() 或 realloc() 配对使用
- free(p); p = NULL; // 释放后置空,防止野指针(指向已释放的内存)
2,内存泄漏
- 始终检查分配结果:调用 malloc() 检查返回指针是否为 NULL,确保内存分配成功
- 成对分配和释放
- 释放后置空为 NULL
- 避免多次释放
- Valgrind 检测内存

被折叠的 条评论
为什么被折叠?



