- Published on
内存管理
- Authors
- Name
- Yanbin
- @ybtaimu
C 语言的内存管理,分成两部分。
一部分是系统管理
的,另一部分是用户手动管理
的。
系统管理的内存
,主要是函数内部的变量(局部变量)
。 这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。 这些变量存放的区域称为”栈“(stack)
,”栈“所在的内存是系统自动管理的
。
用户手动管理的内存
,主要是程序运行的整个过程中都存在的变量(全局变量)
,这些变量需要用户手动从内存释放。 如果使用后忘记释放,它就一直占用内存,直到程序退出,这种情况称为”内存泄漏“(memory leak)
。 这些变量所在的内存称为”堆“(heap)
,”堆“所在的内存是用户手动管理的
。
栈内存 vs 堆内存
C/C++ 中有两种主要的内存分配方式:栈内存和堆内存。
栈内存:当你在函数中声明局部变量时,编译器会自动在栈上为这些变量分配内存。这种分配是自动的,且非常高效,因为栈内存的分配和释放是由编译器管理的,分配和释放的时间非常短(通过调整栈指针)。然而,栈内存有以下限制:
- 栈的大小是有限的,通常只有几 MB。因此,不能在栈上分配大量内存。
- 栈上的变量的生命周期是有限的,它们在函数结束时自动销毁,无法跨函数使用。
堆内存:堆内存是程序运行时的动态内存,程序员可以通过 malloc/free 或 new/delete 来手动管理堆内存。堆内存的优点是可以分配更大的内存块,且内存的生命周期由程序员控制。缺点是堆内存的分配和释放速度比栈慢,且需要程序员手动管理内存,防止内存泄漏。
void指针
前面章节已经说过了,每一块内存都有地址,通过指针变量可以获取指定地址的内存块。 指针变量必须有类型,否则编译器无法知道,如何解读内存块保存的二进制数据。
但是,向系统请求内存的时候,有时不确定会有什么样的数据写入内存,需要先获得内存块,稍后再确定写入的数据类型。
为了满足这种需求,C 语言提供了一种不定类型的指针
,叫做 void 指针
。 它只有内存块的地址信息,没有类型信息,等到使用该块内存的时候,再向编译器补充说明,里面的数据类型是什么。
另一方面,void 指针等同于无类型指针,可以指向任意类型的数据,但是不能解读数据
。
void 指针与其他所有类型指针之间是互相转换关系
,任一类型的指针都可以转为 void 指针,而 void 指针也可以转为任一类型的指针。
int x = 10;
void* p = &x; // 整数指针转为 void 指针
int* q = p; // void 指针转为整数指针
上面示例演示了,整数指针和 void 指针如何互相转换。&x
是一个整数指针,p是 void 指针,赋值时&x
的地址会自动解释为 void 类型。 同样的,p
再赋值给整数指针q
时,p
的地址会自动解释为整数指针。
注意,由于不知道 void 指针指向什么类型的值,所以不能用*运算符取出它指向的值。
char a = 'X';
void* p = &a;
printf("%c\n", *p); // 报错
上面示例中,p是一个 void 指针,所以这时无法用*p取出指针指向的值。
restrict 说明符
声明指针变量时,可以使用restrict说明符,告诉编译器,该块内存区域只有当前指针一种访问方式,其他指针不能读写该块内存。这种指针称为“受限指针”(restrict pointer)
。
int* restrict p;
p = malloc(sizeof(int));
上面示例中,声明指针变量p时,加入了restrict说明符,使得p变成了受限指针。 后面,当p指向malloc()函数返回的一块内存区域,就意味着,该区域只有通过p来访问,不存在其他访问方式。
int* restrict p;
p = malloc(sizeof(int));
int* q = p;
*q = 0; // 未定义行为
上面示例中,另一个指针q与受限指针p指向同一块内存,现在该内存有p和q两种访问方式。这就违反了对编译器的承诺,后面通过*q对该内存区域赋值,会导致未定义行为。