今天小编给大家分享一下C语言柔性数组怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
一、前言
仔细观察下面的代码,有没有看出哪里不对劲?
struct S { int i; double d; char c; int arr[]; };
还有另外一种写法:
struct S { int i; double d; char c; int arr[0]; };
你应该一眼就看到了,结构体的最后一个成员数组的写法是int arr[];或者是int arr[0],这两种写法是等价的,意思是这个数组的大小是不确定的、未知的、可以变化的。
C99允许这种特殊的结构体存在。这样的结构体满足下面两个条件:
1.最后一个成员变量是一个大小可以变化的数组。
2.这个成员数组前面至少有另外一个成员变量。
我们称这个大小可以变化的成员数组为柔性数组。
注意,柔性数组不能是结构体里唯一一个成员,下面的代码是不允许的:
struct S { int arr[0]; };
这篇文章里,我将重点探讨柔性数组的用法、内存分布以及和优势。
二、柔性数组的用法
我不建议在栈上直接定义有柔性数组的结构体,也就是这么写:
struct S s;
因为柔性数组的大小是可以变化的,我建议在堆上申请空间,采取动态内存管理的方法,这样就能发挥出柔性数组大小可以改变的优势。
假设我们使用malloc()函数来开辟空间,一开始应该malloc出多大的空间呢?要回答这个问题,首先我们要知道sizeof(struct S)是多少。
事实上,sizeof(struct S)计算出来的结果是该结构体不考虑柔性数组的大小。如果我们想要给柔性数组开辟空间,malloc出来的大小应该是sizeof(struct S)加上柔性数组的大小。
假设这个柔性数组在结构体中的声明是int arr[0];,我想给这个数组的大小是40个字节,这样这个数组就能存储10个int,那么一开始malloc的大小就应该是sizeof(struct S)+10*sizeof(int),具体的例子如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } return 0; }
该结构体中的i,d,c等变量可以正常使用。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->i = 10; ps->d = 3.14; ps->c = 'F'; return 0; }
柔性数组也可以像正常的数组一样访问,比如把1~10放进去。注意此时这个数组的容量是10个int,不能越界访问。使用的例子如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->i = 10; ps->d = 3.14; ps->c = 'F'; for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); return 0; }
我们还可以对柔性数组扩容,如果我们想让这个柔性数组的容量是20个int,整个结构体的新的大小就是sizeof(struct S)+20*sizeof(int),因为sizeof(struct S)是不考虑柔性数组的大小时计算的结构体大小。只需要对ps进行realloc就行了。实现代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->i = 10; ps->d = 3.14; ps->c = 'F'; for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int)); if (tmp == NULL) { printf("realloc()->%s ", strerror(errno)); return 1; } else { ps = tmp; } return 0; }
扩容后的柔性数组的空间更大了,我们可以把11~20都放进去。实现代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->i = 10; ps->d = 3.14; ps->c = 'F'; for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int)); if (tmp == NULL) { printf("realloc()->%s ", strerror(errno)); return 1; } else { ps = tmp; } for (int i = 10; i < 20; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 20; i++) { printf("%d ", ps->arr[i]); } return 0; }
当然最后别忘了free掉ps,否则会导致内存泄漏。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->i = 10; ps->d = 3.14; ps->c = 'F'; for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int)); if (tmp == NULL) { printf("realloc()->%s ", strerror(errno)); return 1; } else { ps = tmp; } for (int i = 10; i < 20; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 20; i++) { printf("%d ", ps->arr[i]); } free(ps); ps = NULL; return 0; }
对于柔性数组的使用,在上面的例子中,可以总结出几个要点:
1.malloc出来的大小是sizeof(struct S)加上柔性数组的大小,calloc同理。
2.扩容时realloc出来的新大小也是sizeof(struct S)加上柔性数组的新大小。
3.每次使用malloc和realloc等函数时,需要检查返回值,否则可能导致对NULL指针的解引用(这点是动态内存管理的常识了)。
4.一定要记得柔性数组的容量是多少,不要越界访问了,空间不够记得扩容。
5.记得free,防止内存泄漏。
三、柔性数组的内存分布
柔性数组是结构体的一个成员数组,在前面的例子中,整个结构体都是在堆上malloc出来的。此时,整个结构体都存储在堆上的一块连续的空间里,包括前面几个成员变量i,d,c和柔性数组arr。也就是这样:
只不过数组arr的大小是可以改变的,所以叫“柔性数组”。
有些朋友可能会说了,我不需要柔性数组也能实现类似这样的效果呀!我在结构体里存一个指针,指向一块malloc出来的空间,这块空间也是堆上的,可以动态管理。也就是说,像下面这样定义结构体:
struct S { int i; double d; char c; int* arr; };
这样似乎还简单一点,先malloc出一个struct S出来,malloc的大小就是sizeof(struct S),像这样:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } return 0; }
然后再malloc出10个int的大小出来,用结构体中的arr指针来管理这块空间,像这样:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) { printf("2: malloc()->%s ", strerror(errno)); return 1; } return 0; }
此时arr就可以当成一个数组来使用了,比如把1~10放进去。同样还是要注意不要越界访问。示例代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) { printf("2: malloc()->%s ", strerror(errno)); return 1; } for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); return 0; }
你如果觉得空间不够,还可以扩容。比如,你可以把结构体中的arr进行realloc,新的大小能存放20个int。示例代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) { printf("2: malloc()->%s ", strerror(errno)); return 1; } for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); int* tmp = (int*)realloc(ps->arr, 20 * sizeof(int)); if (tmp == NULL) { printf("realloc()->%s ", strerror(errno)); return 1; } else { ps->arr = tmp; } return 0; }
此时,你就可以把11~20也放进去。实现代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) { printf("2: malloc()->%s ", strerror(errno)); return 1; } for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); int* tmp = (int*)realloc(ps->arr, 20 * sizeof(int)); if (tmp == NULL) { printf("realloc()->%s ", strerror(errno)); return 1; } else { ps->arr = tmp; } for (int i = 10; i < 20; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 20; i++) { printf("%d ", ps->arr[i]); } return 0; }
最后别忘了把arr和ps都free掉,而且顺序不能错了。如果你先free掉了ps,结构体就没了,里面的arr就成为了野指针,内存就泄露了。实现代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { printf("malloc()->%s ", strerror(errno)); return 1; } ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) { printf("2: malloc()->%s ", strerror(errno)); return 1; } for (int i = 0; i < 10; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf(" "); int* tmp = (int*)realloc(ps->arr, 20 * sizeof(int)); if (tmp == NULL) { printf("realloc()->%s ", strerror(errno)); return 1; } else { ps->arr = tmp; } for (int i = 10; i < 20; i++) { ps->arr[i] = i + 1; } for (int i = 0; i < 20; i++) { printf("%d ", ps->arr[i]); } free(ps->arr); ps->arr = NULL; free(ps); ps = NULL; return 0; }
那这种实现的内存分布是怎么样的呢?这个结构体是存储在堆上的,用ps来管理,结构体里的一个指针arr又指向了堆上的另一块空间,如下图:
这种实现方式和柔性数组的方式感觉差不多呀!都是在堆上有个结构体,结构体里有个大小可以变化的数组。那为什么非要搞出来个柔性数组的概念呢?那是因为,柔性数组有它独特的优势。
四、柔性数组的优势
前面我们先用柔性数组实现了一种效果,又不使用柔性数组实现了相似的效果,对比两种实现方式,我们可以做一些总结:
1.使用上:柔性数组malloc了一次,free了一次;不使用柔性数组要malloc两次,free两次。柔性数组的使用更加简单,不容易出错。如果不使用柔性数组,可能会忘记free掉结构体里的arr指针,导致内存泄漏。
2.效率上:柔性数组的存储空间是连续的,访问时效率更高。
所以,虽然有相似的效果,我更推荐使用柔性数组的方式。