二:C/C++程序的内存区域的划分
通过以上的题目我们发现,错误基本都是内存泄漏,非法访问,出函数该栈区被释放出现的问题。所以大家一定要弄清楚哪些在堆区,那些在栈区,这些变量会在什么时候销毁等等问题。
栈区(Stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
堆区(heap):由程序员分配释放管理,一般由 malloc,new等内部存储函数使用, 如果没收回,程序结束时由操作系统收回。创建堆时,一般在堆的头部 用一个字节存放堆的大小;回收堆时,通过查看这个 字节的内容,可得知需要释放的多大的内存。
全局区或静态区:存放 全局变量 和 静态变量 ,程序结束时由系统释放,分为全局初始化区和全局未初始化区。
常量区:存放 常量 ,结束时由系统释放。
程序代码区(上面4个区统称数据区):存放运行 或准备运行的程序代码,由系统调度
三:柔性数组:
在 C99 标准中,结构中的最后一个元素的大小允许是未知大小的数组,而我们就将这个未知大小的数组称为柔性数组,并将这个数组成员称为柔性数组成员:
typedef struct A { int a; int arr1[0]; //柔性数组成员 }A; typedef struct B { int b; int arr2[]; //柔性数组成员 }B;
1.柔性数组的特点:
结构中的柔性数组成员前面必须至少存在一个其他成员。
柔性数组只能作为结构的最后一个元素,并且柔性数组的大小是不确定的。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如:
typedef struct st_type { int i; int a[0];//柔性数组成员 }type_a; printf("%d\n", sizeof(type_a));//输出的是4
柔性数组只能作为结构的最后一个元素,并且柔性数组的大小是不确定的。其实家人们在看sizeof()结果的时候就可以知道为什么柔性数组前面必须有其他成员了,因为sizeof返回的结构体大小不包括柔性数组的内存,所以如果没有其他成员的话,该结构体所占空间大小为0.这是不可能的。
2.柔性数组的使用:
既然柔性数组的大小是不确定的,我们如何使用他呢?有了之前的动态内存管理函数的学习,再来研究柔性数组的使用就十分简单了,我们直接来看示例:
typedef struct test { int i; int arr[]; }test; int main() { test a; //定义test类型结构体a test* p = (test*)malloc(sizeof(test) + 40); //malloc函数的返回值为指针类型,故使用结构体指针test* //使用malloc函数动态分配空间:test类型结构体的大小(不包含柔性数组) + 40字节 if (p == NULL) { perror("malloc"); //判断动态内存空间是否开辟成功 return 1; } //业务处理1: p->i = 10; int i = 0; //给柔性数组元素赋值: for (i = 0; i < p->i; i++) { p->arr[i] = i; } //打印柔性数组元素: for(i=0;i<p->i;i++) { printf("%d ", p->arr[i]); } printf("\n"); test* pp = (test*)realloc(p, sizeof(test) + 80); //使用realloc函数将结构指针指向的结构a进行扩容 if (pp == NULL) { perror("realloc"); //判断动态内存空间是否扩容成功 return 1; } //业务处理2: pp->i = 20; for (i = 0; i < pp->i; i++) { pp->arr[i] = i + 9; } for (i = 0; i < pp->i; i++) { printf("%d ", pp->arr[i]); } printf("\n"); free(pp); pp = NULL; return 0; }
在这个示例中,我们首先定义了 test 类型结构体 a,接着使用了 malloc 函数为结构体中的 i与柔性数组分配了动态存储空间;接着在判断非空(动态内存空间分配成功)后给结构体成员 i 与柔性数组 arr 内元素赋值,并进行了打印;再接下来,我们通过使用 realloc 函数将包含柔性数组 arr 的结构体 a 扩容,并在扩容后给结构体内各成员重新赋值并打印;最后释放使用完毕的动态内存空间并将指针置空:
3.柔性数组的优势:
但是同时我们又发现,我们使用柔性数组的目的在于希望使结构成员的空间变为动态,可大可小,那么为什么我们不将其写成指针,由指针成员来指向其它的空间呢?如下:
typedef struct test { int i; int* p; }test; int main() { test* ptr = (test*)malloc(sizeof(test)); if (ptr == NULL) { perror("malloc"); return 1; } ptr->p = (int*)malloc(40); if (ptr->p == NULL) { free(ptr->p); p=NULL; free(ptr); ptr = NULL; return 1; } return 0; }
在这里我们发现,虽然两段代码效果一样,但是大家会发现,柔性数组的只用开辟一次空间,而第二个需要开辟两次!并且释放也是一样。
优点:
方便内存释放。
有利于提升访问速度。
有利于减少内存碎片。
总结:
加上今天内容的学习,我们就可以将动态内存空间灵活的运用在我们的程序代码之中了。并且通过使用并管理动态内存空间,还可以提高我们代码及程序的灵活性和执行效率,有助于我们写出更加优秀的程序。
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!