温馨提示:这篇文章已超过449天没有更新,请注意相关的内容是否还可用!
摘要:本文将探索C语言的内存管理机制,重点解析动态内存管理。我们将深入了解如何在C语言中分配和释放内存,包括使用malloc和free等函数。通过本文,读者将了解如何有效管理C语言程序的内存,避免内存泄漏和其他相关问题,优化程序性能。
🌠先赞后看,不足指正!🌠
🎈这将对我有很大的帮助!🎈
📝所属专栏:C语言知识
📝阿哇旭的主页:Awas-Home page
目录
引言
1. 静态内存
2. 动态内存
2.1 动态内存开辟函数
2.1.1 malloc函数
2.1.2 calloc函数
2.1.3 realloc函数
2.2 动态内存释放函数
2.2.1 free函数
3. 动态内存的常见错误
3.1 对NULL指针的解引用
3.2 对动态开辟空间的越界访问
3.3 对非动态开辟内存使用free释放
3.4 使用free释放一块动态开辟内存的一部分
3.5 对同一块动态内存多次释放
3.6 动态开辟内存忘记释放(内存泄漏)
4. 动态内存管理经典题目分析
4.1 出现悬空指针和访问无效内存
4.2 局部变量的生命周期
4.3 内存开辟后未释放
4.4 内存释放后再次使用
5. 柔性数组
5.1 柔性数组是什么
5.2 柔性数组的特点
5.3 柔性数组的使用
5.4 柔性数组的优势
6. 在C/C++中程序内存区域划分
7. 总结
引言
什么是内存?内存,也称为主存或随机存储器(RAM),是计算机中用于存储数据和程序的临时存储设备。在下文中,我将讲解到内存的开辟方式,分为静态内存开辟和动态内存开辟两部分。
那么,话不多说,我们一起来看看吧!
1. 静态内存
在前面的学习中,我们掌握的内存开辟方式有两种:
#include int main() { int val = 20; //1. 在栈空间上开辟 4 个字节 char a[10] = { 0 };//2. 在栈空间开辟 10 个字节的连续空间 return 0; }
但静态内存开辟的内存空间会存在一定的缺陷:
- 空间开辟大小是固定的。
- 数组在声明的时候,必须指定数组的长度,数组空间一旦确定了大小是不能调整的。
2. 动态内存
为了解决静态内存开辟的内存空间所存在的问题。为此,C语言中引入了动态内存开辟,让程序员可以手动管理内存,包括分配和释放内存空间,这样就比较灵活了。
2.1 动态内存开辟函数
2.1.1 malloc函数
(1)头文件:在标头中定义
(2)函数原型:void* malloc (size_t size);
- size_t 是一个无符号整数类型,size 表示需要分配的字节数。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
(3)作用:向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 开辟成功时,返回指向新分配内存的指针。
- 开辟失败时,返回空指针(NULL),因此 malloc 的返回值一定要做检查。
(4)返回值:该函数返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者来确定。
补充:perror函数
(1)头文件:在标头中定义
(2)函数原型:void perror(const char *s);
- 如果s为NULL,则只输出错误信息而不输出自定义信息。
(3)作用:用于将错误码转换为对应的错误信息并输出到标准错误流(stderr)中。
(4)返回值:无返回值。
#include #include int main() { FILE* fp = fopen("file.txt", "r"); if (fp == NULL) { perror(fp); } return 0; }
运行结果:
malloc 函数的具体使用举例:
int main() { //开辟一个大小为10个整形的空间 //返回值类型强制转换为 (int*) int* arr = (int*)malloc(10 * sizeof(int)); if (arr == NULL)//开辟空间失败 { perror("malloc failed");//打印错误信息 return 1;//返回 } int i = 0; //读取存入数据 for (i = 0; i
输出结果:
监视窗口:读取存入数据
- 动态存储的数据存放在内存的堆区。
2.1.2 calloc函数
(1)头文件:在标头中定义
(2)函数原型:void* calloc (size_t nitems, size_t size);
- nitems表示要分配的元素个数。
- size 表示元素的大小。
(3)作用:分配所需的空间,并返回指向这块空间的指针。
(4)返回值:函数返回一个指向新分配的内存空间的指针,如果分配失败则返回NULL。
calloc函数与malloc函数类似,不同之处在于calloc函数会在分配内存空间后将其初始化为0,而malloc函数不会做这个操作。因此,如果需要分配一段内存空间并将其初始化为0,可以使用calloc函数。
calloc 函数的具体使用举例:
int main() { //开辟一个大小为十个整形的空间 //返回值类型强制转换为 int* int* arr = (int*)calloc(10, sizeof(int)); if (arr == NULL)//开辟空间失败 { perror("calloc failed");//打印错误信息 return 1;//返回 } return 0; }
监视窗口:项全初始化为 0
2.1.3 realloc函数
(1)头文件:在标头中定义
(2)函数原型:void *realloc(void *ptr, size_t size);
- ptr 表示要重新分配内存空间的指针。
- size 表示新分配的内存空间大小。
(3)作用:用于调整 malloc 或 calloc 所分配的 ptr 指向已经分配的内存空间大小。
(4)返回值:函数返回一个指向新分配的内存空间的指针,如果分配失败则返回NULL。
- realloc函数的出现让动态内存管理更加灵活。
- 有时会我们发现过去申请的空间太小或太大了,那么为了合理利用内存,我们会对内存的大小做一些灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
- realloc 函数的扩容机制:
- 原有空间扩容:原有空间之后有足够大的空间,直接在原有内存之后直接追加空间,原来空间的数据不发生变化。
- 新开空间扩容:原有空间之后没有足够大的空间,在堆空间上另找⼀个合适大小的连续空间来使用。同时将新增数据和原有数据拷贝到新的空间,并释放原有空间。
realloc 函数的具体使用举例:
int main() { //开辟一个大小为十个整形的空间 //返回值类型强制转换为 int* int* arr = (int*)calloc(10, sizeof(int)); if (arr == NULL)//开辟空间失败 { perror("calloc failed");//打印错误信息 return 1;//返回 } //扩大空间 //不使用int* arr,是为了防止内存开辟失败,被置为NULL int* tmp = (int*)realloc(arr, 80 * sizeof(int)); if (tmp == NULL)//开辟空间失败 { perror("realloc failed");//打印错误信息 return 1;//返回 } arr = tmp; return 0; }
- 当新增内存比较小时,一般是在原有空间基础上开辟新的空间。二者的地址相同。
int* tmp = (int*)realloc(arr, 80 * sizeof(int));
- 当新增内存比较大时,则会开辟一个新的空间,并将原有空间释放。二者的地址不同。
2.2 动态内存释放函数
2.2.1 free函数
我们要知道,动态开辟的空间不会像静态开辟的空间那样随程序的结束就自动回收。这就需要我们去手动回收,避免造成内存泄漏。
- 内存泄漏是指程序在运行过程中,动态分配的内存空间没有被释放,导致该内存空间一直被占用,无法被其他程序使用,从而造成内存资源的浪费。如果内存泄漏严重,会导致程序崩溃或者操作系统崩溃。
(1)头文件:在标头中定义
(2)函数原型:void free(void *ptr);
- ptr 是指向之前分配的内存空间的指针。调用 free 函数会释放该内存空间,使其可以被重新使用。
(3)作用:用于释放之前通过 malloc、calloc、realloc 等函数动态分配的内存空间。
(4)返回值:无返回值。
free 函数的具体使用举例:
int main() { //开辟一个大小为十个整形的空间 //返回值类型强制转换为 int* int* arr = (int*)calloc(10, sizeof(int)); if (arr == NULL)//开辟空间失败 { perror("calloc fail");//打印错误信息 return 1;//返回 } //扩大空间 int* tmp = (int*)realloc(arr, 20 * sizeof(int)); if (tmp == NULL)//开辟空间失败 { perror("realloc fail");//打印错误信息 return 1;//返回 } arr = tmp; free(arr);//释放arr所指向的动态内存 arr = NULL; return 0; }
- 在使用free函数释放动态分配的内存空间之后,将指针置为NULL是一种良好的编程习惯,可以避免悬空指针的问题。
- 悬空指针是指已经被释放的内存空间的指针,如果在指针被释放后继续使用该指针,就会导致未定义的行为。将指针置为NULL可以避免这种情况的发生,因为在使用空指针时,程序会抛出异常或崩溃,从而提醒程序员出现了问题。
3. 动态内存的常见错误
动态内存的管理和指针类似,利用不当会出现错误,以下是一些常见错误的例子:
3.1 对NULL指针的解引用
void test() { int* pf = (int*)malloc(INT_MAX / 4); *pf = 20; //如果p的值是NULL,就会有问题 free(pf); }
- INT_MAX 是一个宏定义(常量),表示的是整型数据类型的最大值,一般情况下是 2147483647。
- 当 malloc 函数申请的空间过大时,则会出现空间开辟失败的情况,此时返回空指针(NULL)。
- 编译器无法访问空指针(NULL),此时编译器会报错。
正确方法:
void test() { int* pf = (int*)malloc(INT_MAX / 4); if (pf == NULL) { perror("malloc failed!"); return 1; } *pf = 20; //如果p的值是NULL,就会有问题 free(pf); pf = NULL; }
- 综上,检查开辟的空间是否为空指针是十分重要的。
3.2 对动态开辟空间的越界访问
void test() { int i = 0; int* pf = (int*)malloc(10 * sizeof(int)); if (pf == NULL) { perror("malloc failed!"); return 1; } for (i = 0; i len); // 释放内存 free(flexArray); flexArray = NULL; return 0; }
- 这样柔性数组成员 data,相当于获得了100个整型元素的连续空间。
具体使用如下:
#include #include // 定义柔性数组的结构体 typedef struct FlexArray { int len; // 数组的长度 int data[]; // 可变长度的整型数组 }type_a; int main() { int len = 100; // 动态分配内存空间 type_a* pf = malloc(sizeof(type_a) + len * sizeof(int)); if (pf == NULL) { perror("malloc fail!"); return 1; } pf->len = 100; //为柔性数组赋值 for (int i = 0; i data[i] = i; } //输出柔性数组的值 printf("柔性数组的值: "); for (int i = 0; i len; i++) { printf("%d ", pf->data[i]); } printf("\n"); //释放内存 free(pf); pf = NULL; return 0; }
5.4 柔性数组的优势
- 访问元素方便:由于柔性数组是结构体的一部分,因此可以通过结构体指针直接访问柔性数组的元素,而不需要额外的指针操作。
- 提高访问速度:由于柔性数组在内存中是连续存储的,可以提高数据的访问速度,从而提高程序的性能。
- 内存管理效率:柔性数组允许将结构体和数组的内存分配在一次连续的操作中完成,这有助于减少内存碎片化,提高内存管理的效率。
6. 在C/C++中程序内存区域划分
在 C/C++ 程序中,内存区域通常被划分为以下几个部分:
栈区(Stack):
- 栈区用于存储函数的局部变量、函数参数、函数调用的返回地址等。
- 栈区是一种后进先出的数据结构,每次函数调用时会在栈上分配一块内存空间,函数返回时会释放该空间。
- 栈区的大小是有限的,通常在编译时确定,如果栈区溢出会导致程序崩溃。
堆区(Heap):
- 堆区用于动态分配内存,程序员可以通过 malloc、calloc 等函数在堆上分配内存。
- 堆区的大小通常比栈区大得多,可以根据需要动态地分配和释放内存。
- 堆区的内存由程序员手动管理,需要注意避免内存泄漏和内存访问越界等问题。
全局区/静态区(Global/Static):
- 全局区用于存储全局变量、静态变量、常量等。
- 全局区在程序启动时分配,在程序结束时释放。
- 全局变量和静态变量的生命周期与程序的运行周期相同,常量存储在只读内存区域。
7. 总结
希望这篇文章对大家有所帮助,如果你有任何问题和建议,欢迎在评论区留言,这将对我有很大的帮助。
完结!咻~
- 这样柔性数组成员 data,相当于获得了100个整型元素的连续空间。
- 综上,检查开辟的空间是否为空指针是十分重要的。
- ptr 是指向之前分配的内存空间的指针。调用 free 函数会释放该内存空间,使其可以被重新使用。
- 内存泄漏是指程序在运行过程中,动态分配的内存空间没有被释放,导致该内存空间一直被占用,无法被其他程序使用,从而造成内存资源的浪费。如果内存泄漏严重,会导致程序崩溃或者操作系统崩溃。
- 当新增内存比较大时,则会开辟一个新的空间,并将原有空间释放。二者的地址不同。
- 当新增内存比较小时,一般是在原有空间基础上开辟新的空间。二者的地址相同。
- 动态存储的数据存放在内存的堆区。
- 如果s为NULL,则只输出错误信息而不输出自定义信息。
还没有评论,来说两句吧...