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

相关推荐
小白学大数据42 分钟前
长周期爬虫的数据一致性:断点续爬 + 事务回滚保障采集质量
开发语言·爬虫·测试工具
zlinear数据采集卡1 小时前
D223上位机C#开发实战:从帧解析到波形显示的完整实现
开发语言·arm开发·嵌入式硬件·fpga开发·开源·c#
Poo_Chai2 小时前
QT emit信号后完整处理流程,包括槽函数响应流程
java·开发语言·数据库
瑞码空间2 小时前
Java 图形界面(GUI)完整知识点手册
java·开发语言·图形界面·swing
小努蛋2 小时前
linux编译openresty异常问题,lua_cjson.c:706:5: 错误: ‘for‘ 循环初始化声明只在 C99 或 C++ 模式下允许
开发语言·lua·openresty
qz_Serene2 小时前
C++:类和对象(上)
开发语言·c++
西安景驰电子3 小时前
《PTP精确时间协议系列》第一篇:从原理到应用,全面解读IEEE 1588
linux·运维·服务器·开发语言·网络·windows·php
ctlover3 小时前
Python文件操作
开发语言·python
luj_17683 小时前
塔防牌:策略与卡牌的智慧碰撞
服务器·c语言·开发语言·经验分享·算法