C语言中的柔性数组

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语言为了高效处理"变长结构体"而设计的特性

相关推荐
晓蛋3 天前
c语言指的是什么意思
c语言·编译器·编程开发·集成开发环境·程序实例
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
傲世仙尊3 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
牵猫散步的鱼儿3 天前
重载、重写(覆盖)、重定义区别
c语言
伞伞悦读3 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
phltxy3 天前
C 语言指针:从内存地址到灵活的数据访问
c语言
C语言小火车3 天前
C/C++ 为什么需要编译器?
开发语言·c++
phltxy3 天前
C 语言中的数据存储:从类型到二进制位
c语言
霍霍的袁3 天前
【C++】map 和 set 的使用 | 从用法到底层
开发语言·c++·学习·visual studio
孙启超3 天前
【AI开发之Rust】第 11 课:智能指针与内部可变性
开发语言·后端·rust