C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(中):https://developer.aliyun.com/article/1513205
5. C/C++程序的内存开辟
C/C++程序内存分配的几个区域:
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,
函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,
效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、
函数参数、返回数据、返回地址等。
2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
分配方式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
内存区域划分图:
有了这幅图,我们就可以更好的理解在前面讲的static关键字修饰局部变量的例子了。
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,
直到程序结束才销毁,所以生命周期变长。
栈区的特点:在上面创建的变量出了作用域就销毁。
数据段的特点:在上面创建的变量直到程序结束才销毁。
6. 柔性数组 flexible array
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
定义:柔性数组(Flexible Array),又称可变长数组。一般数组的长度是在编译时确定,而柔性数组对象的长度在运行时确定。在定义结构体时允许你创建一个空数组(例如:arr [ 0 ] ),该数组的大小可在程序运行过程中按照你的需求变动。
【百度百科】在 ANSI 的标准确立后,C语言的规范在一段时间内没有大的变动,
然而C++在自己的标准化创建过程中继续发展壮大。《标准修正案一》在1994年为C语言创建了一个新标准,但是只修正了一些C89标准中的细节和增加更多更广的国际字符集支持。不过,这个标准引出了1999年ISO 9899:1999的发表。被称为C99,C99被ANSI于2000年3月采用。
演示:
typedef struct st_type { int i; int a[0];//柔性数组成员 }type_a;
部分编译器可能会报错,可以试着将 a [ 0 ] 改为 a [ ] :
typedef struct st_type { int i; int a[];//柔性数组成员 }type_a;
6.1 柔性数组的特点:
1.结构中的柔性数组成员的前面必须至少有一个其他成员:
typedef struct st_type { int i;//必须至少有一个其他成员 int a[0];//柔性数组成员 }type_a;
2.sizeof 计算这种结构的大小是不包含柔性数组成员的:
#include <stdio.h> struct S { int n; // 4 int arr[]; // 大小是未知的 }; int main() { struct S s = { 0 }; printf("%d\n", sizeof(s));//4 return 0; }
3.包含柔性数组成员的结构,用 malloc 函数进行内存分配,
并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小:
#include <stdio.h> #include <stdlib.h> struct S { int n; int arr[0]; }; int main() { //期望arr的大小是10个整型 给n的 给arr[]的 struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); // 后面+的大小就是给柔性数组准备的 return 0; }
6.2 柔性数组的使用
代码1:使用柔性数组
#include <stdio.h> #include <stdlib.h> struct S { int n; int arr[0]; }; int main() { // 期望arr的大小是10个整型 struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); if (ps == NULL) { printf("malloc fail\n"); return -1; } ps->n = 10; // 使用 for (int i = 0; i < 10; i++) { ps->arr[i] = i; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } // 增容 struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int)); if (ptr != NULL) { ps = ptr; } // 再次使用 (略) // 释放 free(ps); ps = NULL; return 0; }
6.3 柔性数组的优势
代码2:直接使用指针
想让n拥有自己的空间,其实不使用柔性数组也可以实现。
#include <stdio.h> #include <stdlib.h> struct S { int n; int* arr; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { printf("malloc fail\n"); return -1; } ps->n = 10; ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) { printf("malloc fail\n"); return -1; } // 使用 for (int i = 0; i < 10; i++) { ps->arr[i] = i; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } // 增容 int* ptr = (struct S*)realloc(ps->arr, 20 * sizeof(int)); if (ptr != NULL) { ps->arr = ptr; } // 再次使用 (略) // 释放 free(ps->arr); // 先free第二块空间 ps->arr = NULL; free(ps); ps = NULL; return 0; }
虽然 代码2 实现了相应的功能,但是和 代码1 比还是有很多不足之处的。代码2 使用指针完成,
进行了两次 malloc ,而两次 malloc 对应了两次 free ,相比于 代码1 更容易出错
上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:
第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体, 但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果把结构体的内存以及其成员要的内存一次性分配好,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第二个好处是:这样有利于访问速度.
连续内存多多少少有益于提高访问速度,还能减少内存碎片。malloc 的次数越多,
产生的内存碎片越多,这些内存碎片不大不小, 再次被利用的可能性很低。内存碎片越多,
内存的利用率就会降低。频繁的开辟空间效率会变低,碎片也会增加。
(其实也没多高了,反正跑不了要用做偏移量的加法来寻址)内存碎片和内存池:
扩展阅读:
C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell
本篇完。
可以自己模拟实现:atoi + strncat + strncpy。