本章重点
- 什么是动态内存
- 为什么要有动态内存
- 什么是野指针
- 对应到C空间布局, malloc 在哪里申请空间
- 常见的内存错误和对策
- C中动态内存“管理”体现在哪
什么是动态内存
- 动态内存是指在程序运行时,根据需要动态分配的内存空间。
#include <stdio.h> #include <stdlib.h> #include <malloc.h> #define N 10 int main() { int* p = (int*)malloc(sizeof(int) * N); //动态开辟空间40个字节的空间 if (NULL == p) { //判断是否成功申请空间 perror("malloc\n"); //perror函数是标准库函数,其作用是输出上一个系统调用(例如malloc)的错误信息 exit(0); //exit函数结束程序运行 } for (int i = 0; i < N; i++) { p[i] = i;//赋值0,1,2,3,4,5,6,7,8,9 } for (int i = 0; i < N; i++) { printf("%d ", i);//打印 } printf("\n"); free(p); //开辟完之后,要程序员自主释放 p = NULL; return 0; }
为什么要有动态内存
- 通常,在编写程序时,我们可以使用静态内存(静态分配内存),也就是在程序编译阶段就确定了内存空间的大小和位置,但是静态内存存在一定的限制和局限性,比如无法在运行时改变分配的内存大小等。
- 而动态内存则具有更大的灵活性和可变性,可以在程序运行时动态地分配、释放内存,以适应程序的实际需求。
栈、堆和静态区
C程序动态地址空间分布
#include <stdio.h> #include <stdlib.h> #include <malloc.h> int g_val2;//未初始化变量 int g_val1 = 1;//已初始化变量 int main() { printf("code addr: %p\n", main);//代码区 const char* str = "hello world";//字符常量区 printf("static readonly: %p\n", str);//这里输出的是字符串的首地址 printf("init(初始化) global val: %p\n", &g_val1);//已初始化变量区 printf("uninit(未初始化) global val: %p\n", &g_val2);//未初始化变量区 int* p = (int*)malloc(sizeof(int) * 10); printf("heap(堆) : %p\n", p);//这里输出的是开辟40个字节空间的首地址 //输出两个局部指针变量的地址 printf("stack(栈) addr: %p\n", &str); printf("stack(栈) addr: %p\n", &p); }
由于win中有地址随机化保护,我们这里的结果是再Linux中验证的
同时我们也可以发现栈是向下增长的,后定义的变量后入栈,其相应的地址也较小。
再来验证一下堆区的特点。
char* p1 = (char*)malloc(sizeof(char) * 10); printf("heap(堆) : %p\n", p1); char* p2 = (char*)malloc(sizeof(char) * 10); printf("heap(堆) : %p\n", p2); char* p3 = (char*)malloc(sizeof(char) * 10); printf("heap(堆) : %p\n", p3); printf("stack(栈) addr: %p\n", &p1); printf("stack(栈) addr: %p\n", &p2); printf("stack(栈) addr: %p\n", &p3);
堆是符合向上增长的,先开辟的变量,其相应的地址较小。
在C语言中,为何一个临时变量,使用static修饰之后,它的生命周期变成全局的了?
当在一个函数中将一个局部变量添加了static关键字时,编译器会将其转化为对应的静态变量,这使得该变量的存储位置从栈(stack)转变为全局数据区(data segment)中的静态变量存储区,使得该变量在函数调用结束后不会被自动销毁。
常见的内存错误及对策
ONE:指针没有指向一块合法的内存
1、结构体成员指针未初始化
2、没有为结构体指针分配足够的内存
3、函数的入口检测
TWO:为指针分配的内存太小
THREE:内存分配成功,但并未初始化
FOUR:内存越界
FIVE:内存泄漏
- 申请内存是在哪里申请?- 堆
- 申请内存是向谁要空间?- 操作系统
- 如何申请内存? - malloc函数
- 申请内存是否需要释放?如何释放? - 需要,free函数
- 申请内存不释放会有什么问题? - 内存泄露
程序退出的时候,曾经的内存泄漏问题还存在吗?
内存释放的本质是什么?
观察free函数的参数,free函数只知道释放空间的起始地址,貌似并不知道要释放多大空间,那如何正确释放呢?
我们这里写一个单链表代码来演示动态开辟内存
#include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <windows.h> #define N 10 typedef struct _Node { int data; struct _Node* next; }node_t; static node_t* AllocNode(int x) { node_t* n = (node_t*)malloc(sizeof(node_t)); if (NULL == n) { exit(EXIT_FAILURE); } n->data = x; n->next = NULL; return n; } void InsertList(node_t* head, int x) { node_t* end = head; while (end->next) { end = end->next; } node_t* n = AllocNode(x); end->next = n; } void ShowList(node_t* head) { node_t* p = head->next; while (p) { printf("%d ", p->data); p = p->next; } printf("\n"); } void DeleteList(node_t* head) { node_t* n = head->next; if (n != NULL) { head->next = n->next; free(n); } } int main() { node_t* head = AllocNode(0); //方便操作,使用带头结点的单链表 printf("插入演示...\n"); Sleep(10000); for (int i = 1; i <= N; i++) { InsertList(head, i); //插入一个节点,尾插方案 ShowList(head); //显示整张链表 Sleep(1000); } printf("删除演示...\n"); for (int i = 1; i <= N; i++) { DeleteList(head); //删除一个节点,头删方案 ShowList(head); //显示整张链表 Sleep(1000); } free(head); //释放头结点 head = NULL; return 0; }
SIX:内存已经被释放了,但是继续通过指针来试用
C中动态内存“管理”体现在哪