本章重点
- 什么是动态内存
- 为什么要有动态内存
- 什么是野指针
- 对应到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中动态内存“管理”体现在哪