C语言中,为了实现变长结构体,引入了柔性数组,既省内存又比指针方案更高效更安全。本文将主要来认识柔性数组。
一、柔性数组的定义
C99中,结构中的最后一个元素可以是一个大小为0数组,这个数组就是柔性数组。
定义方式如下:
c
struct struct_arr{
int a;
int b[];//柔性数组成员,有的编译器要求写成 int b[0]
};
柔性数组"int b\[\]"还是"int b0"主要看编译器,有的编译器两种都支持。
上面的数组名b,代表一个地址标签 ,(类似struct这个标签),是整个结构体的延伸部分的一个别名,不占据存储空间。
如果把b\[\]处换为"int *b",对于指针b需要为它开辟一个8字节的空间(前提64位的操作系统)。两者的差距可参考下面实际运行结果:
c
#include<stdio.h>
#include<windows.h>
#include<math.h>
struct struct_arr0{
int a;
int b[];
};
struct struct_arr1{
int c;
int *d;
};
int main(){
SetConsoleOutputCP(65001);
SetConsoleCP(65001);
struct struct_arr0 sa0;
struct struct_arr1 sa1;
printf("struct_arr0结构体大小:%zu字节\n",sizeof(sa0));
printf("struct_arr1结构体大小:%zu字节\n",sizeof(sa1));
return 0;
}
运行结果如下:

| 对比项 | 柔性数组 | 指针 |
|---|---|---|
| 申请/释放次数 | 一次malloc,一次free | 要两次malloc/free |
| 内存情况 | 内存连续,访问更快(CPU缓存友好),序列化/网络传输方便 | 数据和结构体不连续,缓存不友好 |
| 潜在风险 | 不会泄漏 | 容易内存泄漏、野指针 |
| 缺点 | 数组长度分配以后不能动态改变 | 可以后期重新realloc改变数据长度 |
二、柔性数组的使用
如上面的b\[\],在a后面就是b,后面我们可以使用malloc申请一段任意大小的空间,用b去访问那段空间(即:操作灵活,同一个类型的结构体指针,可以操作的空间大小可变)
c
#include<stdio.h>
#include<windows.h>
#include<math.h>
struct struct_arr0{
int a;
int b[];
};
struct struct_arr1{
int c;
int *d;
};
int main(){
SetConsoleOutputCP(65001);
SetConsoleCP(65001);
struct struct_arr0 *sa1=malloc(sizeof(struct struct_arr0)+2*sizeof(int));//此时b的空间大小就是8字节
sa1->a=1000;
*(sa1->b)=5;
*(sa1->b+1)=100;
printf("=====柔性数组====\n");
printf("结构体-柔性数组中a:%d\n",sa1->a);
printf("结构体-柔性数组中b[0]里存储的是:%d\n",*(sa1->b));
printf("结构体-柔性数组中b[1]里存储的是:%d\n",*(sa1->b+1));
printf("sa1结构体的首地址是:%p\n",(void *)sa1);
printf("结构体-柔性数组中a地址的是:%p\n",(void *)&(sa1->a));
printf("结构体-柔性数组中b地址的是:%p\n",(void *)&sa1->b[0]);
printf("结构体-柔性数组中b[1]里地址的是:%p\n",(void *)&sa1->b[1]);
struct struct_arr1 *sa2=malloc(sizeof(struct struct_arr1));//第一次malloc,结构体本身
sa2->d=malloc(2*sizeof(int));//第二次malloc,d指向独立内存
sa2->c=2000;
sa2->d[0]=8;
sa2->d[1]=300;
printf("=====指针版本====\n");
printf("结构体-指针中c:%d\n",sa2->c);
printf("结构体-指针中d[0]里存储的是:%d\n",sa2->d[0]);
printf("结构体-指针中d[1]里存储的是:%d\n",sa2->d[1]);
printf("sa2结构体的首地址是:%p\n",(void *)sa2);
printf("结构体-指针中c地址的是:%p\n",(void *)&(sa2->c));
printf("结构体-指针中d的地址的是:%p\n",(void*)sa2->d);
printf("结构体-指针中d[0]内容的地址的是:%p\n",(void *)&sa2->d[0]);
printf("结构体-指针中d[1]内容的地址的是:%p\n",(void *)&sa2->d[1]);
free(sa1);
free(sa2->d);
free(sa2);
return 0;
}
运行结果:

sa1->b:b0、b1全部来自一次malloc,释放free(sa1)即可,只free一次
sa2->d:仅存一个地址,d指向另外一个malloc出来的独立堆块,需要malloc2次,释放要先free(sa2->d),再free(sa2),忘记释放d直接free(sa2)直接内存泄漏
二、柔性数组的注意事项
1、柔性数组成员必须放在结构体最后
三、对比其他语言
柔性数组只有C、C++有,在嵌入式、底层开发场景使用
C++:不推荐柔性数组,用std:vector
Java:没有柔性数组,用List、动态数组
python/go:自带动态数组,不需要这种语法
总之:柔性数组是C语言为了高效处理"变长结构体"而设计的特性