前言
前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象。也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C和C++比较重要的知识点——内存管理。下面话不多说坐稳扶好咱们要开车了😍
一、C/C++内存分布
C/C++的内存分布较为复杂,大概分为五个部分它们分别是:栈(Stack)、堆(Heap)、全局区/静态区(Global Area/Static Area)、常量区(Constant Area)、代码区(Code Area)下面我会分别介绍他们各自的属性。
1. 栈(Stack)
🍟栈是用来存储函数的局部变量、函数参数和函数调用的返回地址等信息的内存空间。
🍟栈空间由编译器自动管理,它的分配和释放是自动进行的,不需要程序员手动操作。
🍟每当一个函数被调用时,编译器会在栈上为该函数分配一块特定大小的空间,称为函数帧或活动记录(Activation Record)。
🍟栈是一种高效的存储方式,由于栈上的内存是连续分配的,因此访问速度较快。然而,栈的大小是有限的,过多的栈帧可能导致栈溢出的错误。
2. 堆(Heap)
🍁堆是用来动态分配内存的空间,通过使用malloc、free、new、delete等操作来分配和释放内存。
🍁堆的分配是手动进行的,程序员需要显式地分配和释放内存。
🍁堆是一种灵活的存储方式,允许动态调整分配的内存大小。
🍁堆上的内存分配通常是不连续的,因此访问速度稍慢于栈。
🍁如果使用不当,堆上的内存可能导致内存泄漏或内存碎片化等问题。
3. 全局区/静态区(Global Area/Static Area)
🍔全局区用来存储全局变量和静态变量。
🍔全局变量在程序开始执行时分配,在程序结束时被释放,因此它们的生命周期与整个程序的运行周期相同。
🍔静态变量有不同的存储位置和生命周期,具体取决于其作用域和存储类型(例如静态局部变量、静态全局变量)。
🍔全局区的内存空间在程序启动时由操作系统分配,并在程序退出时由操作系统释放。
4. 常量区(Constant Area)
🍪常量区用来存储常量数据,例如字符串字面量和其他常量。
🍪常量区的内存通常是只读的,因此程序不能对其进行修改。
🍪常量区的数据在程序运行期间保持不变。
5. 代码区(Code Area)
🥝代码区用来存储程序的机器指令,也称为可执行代码。
🥝代码区通常是只读的,存储程序的执行逻辑。
🥝代码区的内存空间在程序启动时被分配,并且在程序运行期间保持不变。
🚨注意:具体的内存布局和分配方式可能因编译器、操作系统和硬件平台的不同而有所差异。此外,在多线程和多进程的情况下,还需要考虑线程栈、共享内存等特殊情况。
二、C语言中动态内存管理方式
1. malloc函数
- malloc 函数用于动态地分配指定大小的内存空间。
- 它接受一个参数,即需要分配的内存大小(以字节为单位),并返回所分配内存的起始地址。
int ptr = (int)malloc(sizeof(int));
- malloc 函数在堆上找到足够大的连续内存空间进行分配,如果找不到足够大的连续空间,则返回 NULL
- 分配的内存是未初始化的,即其中的数据是不确定的,需要手动初始化。
#include <stdio.h> #include <stdlib.h> int main() { int size; int* arr; printf("Enter the size of the array: "); scanf("%d", &size); // 使用malloc函数动态分配内存 arr = (int*)malloc(size * sizeof(int)); if (arr == NULL) { printf("Memory allocation failed!"); return 1; } printf("Enter %d elements:\n", size); for (int i = 0; i < size; i++) { scanf("%d", &arr[i]); } printf("The elements you entered are: "); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } // 释放已分配的内存 free(arr); return 0; }
上面的程序实现了以下步骤:
- 用户输入数组的大小。
- 使用 malloc 函数动态分配足够大的内存空间来存储整数数组。
- 检查内存分配是否成功,如果分配失败,则输出错误信息并结束程序。
- 用户输入数组的元素。
- 打印用户输入的数组元素。
- 使用 free 函数释放分配的内存空间。
2. calloc函数
- calloc 函数用于动态地分配指定数量和大小的内存空间,并将其初始化为0。
- 它接受两个参数,第一个是需要分配的元素数量,第二个是每个元素的大小(以字节为单位)。
int ptr = (int)calloc(5, sizeof(int));
- calloc 函数在堆上找到足够大的内存空间进行分配,并将所有字节初始化为0。
- 分配的内存是初始化过的,不需要手动初始化。
#include <stdio.h> #include <stdlib.h> int main() { int size; int* arr; printf("Enter the size of the array: "); scanf("%d", &size); // 使用calloc函数动态分配内存,并将内存初始化为0 arr = (int*)calloc(size, sizeof(int)); if (arr == NULL) { printf("Memory allocation failed!"); return 1; } printf("Enter %d elements:\n", size); for (int i = 0; i < size; i++) { scanf("%d", &arr[i]); } printf("The elements you entered are: "); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } // 释放已分配的内存 free(arr); return 0; }
上面的代码功能跟上面 malloc 函数的示例表达的功能一样这里不做过多的解释,记得在完成使用动态分配的内存后,使用 free 函数释放已分配的内存,以免出现内存泄漏。
3. realloc函数
- realloc 函数用于重新调整之前分配的内存空间的大小。
- 它接受两个参数,第一个是之前分配内存的起始地址,第二个是需要调整的新大小(以字节为单位)
ptr = (int*)realloc(ptr, 10 * sizeof(int));
- realloc 函数将尝试在原始内存空间上重新调整大小,如果成功,则返回调整后的内存地址;如果原始内存空间不够大或者其他错误,则返回 NULL
- 若新大小大于原始大小,则新增的字节未初始化;若新大小小于原始大小,则超出新大小的部分将被丢弃。
#include <stdio.h> #include <stdlib.h> int main() { int size; int* arr; printf("Enter the size of the array: "); scanf("%d", &size); // 使用malloc函数动态分配内存 arr = (int*)malloc(size * sizeof(int)); if (arr == NULL) { printf("Memory allocation failed!"); return 1; } printf("Enter %d elements:\n", size); for (int i = 0; i < size; i++) { scanf("%d", &arr[i]); } // 重新分配内存,将数组大小调整为10 int newSize = 10; int* newArr = (int*)realloc(arr, newSize * sizeof(int)); if (newArr == NULL) { printf("Memory reallocation failed!"); free(arr); // 释放之前分配的内存 return 1; } // 更新指针arr的引用 arr = newArr; // 打印数组元素 printf("The elements you entered are: "); for (int i = 0; i < newSize; i++) { printf("%d ", arr[i]); } // 释放分配的内存 free(arr); return 0; }
首先使用 malloc 函数动态分配内存,并在之后的 realloc 操作中进行了适当的错误处理。如果 realloc 函数返回 NULL ,表示内存调整失败,此时会输出错误信息并释放之前分配的内存。如果 realloc 函数成功,则将新分配的内存地址赋给 arr 指针,并继续使用调整后的内存。在最后,使用 free 函数释放分配的内存空间,并返回 0 表示程序正常结束
4. free函数
- free 函数用于释放之前动态分配的内存空间。
- 传入 free 函数的参数是之前分配的内存块的起始地址。
- 调用free函数将释放所指定的内存空间,使其可供之后的malloc、calloc、realloc等函数重新使用。
这些动态内存管理函数提供了灵活的内存分配和释放能力,可以根据需要动态调整内存的大小。然而,需要确保正确使用,避免内存泄漏和野指针等问题。建议在每次分配内存后检查分配是否成功,以及在适当的时候使用 free 函数释放不再需要的内存。
void Test () { int* p1 = (int*) malloc(sizeof(int)); free(p1); int* p2 = (int*)calloc(4, sizeof (int)); int* p3 = (int*)realloc(p2, sizeof(int)*10); // 这里需要free(p2)吗? 答:不用 free(p3 ); }
上面的代码中不需要 free(p2) 因为当调用realloc函数时,如果成功调整了内存大小,那么原始内存区域的内容将被复制到新分配的内存,并且原始的内存空间会被释放。因此,在这种情况下,不需要再手动调用 free(p2) 来释放原始的内存空间,因为realloc函数已经负责处理了。