第二章 内存管理
operator new 和 operator delete
内存五大分区
五大内存分区是指计算机内存(RAM)在程序运行时被划分为五个不同的区域,分别是代码段(text segment)、数据段(data segment)、BSS段、堆(heap)和栈(stack)。这种划分方式是在传统的C语言内存模型中提出的,现在已经成为了计算机科学中的基础知识之一。
这五个内存分区的主要特点和用途如下:
-
代码段(text segment):存储程序的指令代码,也称为文本段或代码段。这些指令被处理器执行,控制程序的运行。
-
数据段(data segment):存储已初始化的全局变量和静态变量,包括全局变量、静态变量和常量数据等。这些变量在程序运行期间可以被读取和修改。
-
BSS段:存储未初始化的全局变量和静态变量,也称为未初始化数据段(uninitialized data segment)。这些变量在程序运行前被初始化为0或者空指针,然后在程序运行期间可以被读取和修改。
-
堆(heap):动态分配内存的区域,用于存储程序运行时动态分配的内存块。程序员可以通过调用malloc()、realloc()等函数在堆上分配内存。
-
栈(stack):存储函数调用时的局部变量和函数参数,以及函数调用的上下文信息,例如函数返回地址和寄存器的值等。栈是由系统自动分配和释放的,每当函数被调用时,栈就会分配一段内存空间,函数返回后栈就会自动释放这段空间。
这五大内存分区在程序运行期间发挥着不同的作用,了解它们的特点和用途对于程序员编写高效、安全的代码非常重要。
注意
代码段也称为文本段(text segment)是程序中存放机器指令的内存区域,包含程序执行的指令和常量数据(如字符串常量)。
代码段通常是只读的,因为程序在运行时不应该修改代码段中的指令。此外,代码段通常是共享的,这意味着多个进程可以共享同一份代码段,从而实现代码的共享和节约内存的效果。
在一些系统中,代码段也可能包含一些只读的数据(如常量或只读的变量),这些数据可以在运行时被访问但不能被修改。
总的来说,代码段包含了程序执行所需的指令和常量数据,并且通常是只读和共享的。
数据段也称为已初始化数据段(initialized data segment),是程序中存放静态和全局变量的内存区域,它包含了程序在编译时就已经分配好了内存的变量和常量数据,它存储已经被初始化的全局变量和静态变量,包括常量数据。这些变量在程序运行期间可以被读取和修改,数据段通常是可读写的。
BSS段也称为未初始化数据段(uninitialized data segment),它存储未初始化的全局变量和静态变量。BSS段在程序运行前会被初始化为0或空指针,然后在程序运行期间可以被读取和修改。BSS段通常是可读写的。
这些术语通常用于描述传统的C语言内存模型中的内存布局,而不同的编程语言和操作系统可能会采用不同的术语和内存布局。
常量区通常被认为是数据段的一部分,属于五大内存分区中的数据段(data segment)。数据段包括已初始化的全局变量、静态变量和常量数据,这些数据在程序运行期间可以被读取和修改。常量区存储的是不可修改的常量数据,通常在程序运行期间不会被修改,因此常量区也被视为数据段的只读部分。
来道题
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段 D.代码段
globalVar在哪里?____C staticGlobalVar在哪里?____C
staticVar在哪里?____C localVar在哪里?____A
num1 在哪里?____A
char2在哪里?____A *char2在哪里?___A
pChar3在哪里?____A *pChar3在哪里?____D
ptr1在哪里?____A *ptr1在哪里?____B
2. 填空题:
sizeof(num1) = ____;40
sizeof(char2) = ____;5 strlen(char2) = ____;4
sizeof(pChar3) = ____;4或8 strlen(pChar3) = ____;4
sizeof(ptr1) = ____;4或8
重温
C语言的三种动态内存管理的函数
malloc
申请指定大小的空间(未初始化,随机值)
int* pi = (int*)malloc(sizeof(int) * 1); //申请一个整型
double* pd = (double*)malloc(sizeof(double) * 2); //申请两个浮点型
char* pc = (char*)malloc(sizeof(char) * 3); //申请三个字符型
calloc
申请的空间初始化为 0
int* pi = (int*)calloc(1, sizeof(int)); //申请一个整型
double* pd = (double*)calloc(2, sizeof(double)); //申请两个个浮点型
char* pc = (char*)calloc(3, sizeof(char)); //申请三个字符型
realloc
对已申请的空间进行扩容
int* tmp = (int*)realloc(pi, sizeof(int) * 10); //将 pi 扩容为十个整型
注意:
我们要对所有的申请函数进行空指针检查,预防野指针问题
凡是动态开辟的空间,用完后都需要释放
free
free(tmp); //此时tmp指向pi扩容后的空间,释放tmp就行了
tmp = NULL; // 置空
free(pd);
pd = NULL;
free(pc); //只要是动态开辟的,都需要通过 free 释放
pc = NULL;
注意:
只有动态开辟的空间才能使用 free,同一块空间不能释放两次。我们在 free 后通常会把指针置空
C语言 中管理函数只能对内置类型使用,而 C++ 中存在很多自定义类型,常规 malloc 等函数无能为力
C++内存管理方式
通过new和delete操作符进行动态内存管理
使用new是动态分配内存,这个分配的是堆的内存,需要用户自己手动释放,即通过delete释放;而不使用new的对象在栈的空间中,在当前作用于结束后自动回收。
new创建出的对象需要使用指针接收。
new/delete操作内置类型
int* ptr1 = new int;//动态开辟一个int空间
delete ptr1;
int* ptr2 = new int(10);//动态申请一个int类型的空间并初始化为10
delete ptr2;
int* ptr3 = new int[3];//申请3个int类型的空间
delete[] ptr3;
int* ptr4 = new int[3] {1, 2, 3};//申请3个int类型的空间并进行初始化
delete[] ptr4;
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],注意:需要配套使用
new/delete操作自定义类型
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与
free不会
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
operator new 和 operator delete
new 和 delete 是用户进行动态内存申请和释放的 操作符,它们在实现时会去调用真正的全局函数 operator new 与 operator delete,operator new 和operator delete是系统提供的全局函数。
operator new 实际也是通过malloc来申请空间
operator delete 最终是通过free来释放空间的
只是在 开辟或释放 失败时会抛出异常。
定位new(placement new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
A* p1 = (A*)malloc(sizeof(A));
if (p1 == nullptr)
{
perror("malloc fail");
exit(-1);
}
// 定位new -- 对p3指向空间,显示调用构造函数初始化
new(p1)A(1);
delete p1;
return 0;