在刚开始写C代码的时候,我们可能会遇到这样一种情况:要存储一组同学的名字,可是每个同学的姓名长度不同,用固定大小的数组会很不方便而且会存在浪费内存的情况。这个时候我们引入了动态数组,通过malloc()函数申请内存按需分配大小。这时候问题来了,如果有一个学生的结构体需要存储姓名这样的属性我们该怎么做呢?同样的我们会在结构体中加入一个char*的指针成员.
struct Student {int id;int age;char sex;char* name;}
这样做比较麻烦的有两点:
- name是一个指针,我们需要手动的创建和回收,防止内存泄漏;
- name在调试过程中是无法看到全貌的,只能看到第一个字节或者是通过表达式打印出整个名字.
有没有什么比较漂亮的解决方案呢?那就是C语言从C99标准开始引入的一个”柔性数组”(Flexible Array Members)的功能,主要在结构体中增加一个可变大小的灵活数组.
先看一下使用柔性数组的写法:
struct Student {int id;int age;int score;char name[0];}
乍一看,好像和普通数组没有区别,但是数组的大小竟然为0,也可以写作char name[],这就是柔性数组.
柔性数组的使用
创建:如果Student结构体需要需要存储一个长度为len的人名,我们只需下面这样写即可:struct Student *pStu = malloc(sizeof(Student)) + len * sizeof(char))
释放:当我们在释放内存的时候,和正常的结构体一样即可:free(pStu);
柔性数组的使用要求
- 必须支持C99标准及以后的编译器;
- 结构体内部的数组声明必须是最后一个成员;
- 出了FAM数组成员,结构体中还必须包含一个其它命名成员.
柔性数组的解释
柔性数组本质上是把指针的动态数组申请的内存放在了结构体的尾部,从而使得结构体成员和动态申请的数组是串联在一起的,于是可以直接使用结构体的指针取访问这块内存.name成员只作为一个符号地址的存在指向结构体的地址,并没有占用实际的内存空间,因此sizeof(Student)的大小并不包括数组的内存大小.
柔性数组的使用案例
在linux内核中就有很多地方使用了0长数组,下面举一个内核中USB驱动代码的例子:
struct urb {struct kref kref;void *hcpriv;atomic_t use_count;atomic_t reject;int unlinked;struct list_head urb_list;struct list_head anchor_list;struct usb_anchor *anchor;struct usb_device *dev;struct usb_host_endpoint *ep;unsigned int pipe;unsigned int stream_id;int status;unsigned int transfer_flags;void *transfer_buffer;dma_addr_t transfer_dma;struct scatterlist *sg;int num_mapped_sgs;int num_sgs;u32 transfer_buffer_length;u32 actual_length;unsigned char *setup_packet;dma_addr_t setup_dma;int start_frame;int number_of_packets;int interval;int error_count;void *context;usb_complete_t complete;struct usb_iso_packet_descriptor iso_frame_desc[0];};
usb_iso_packet_descriptor就是一个柔性数组,由于不同场景的缓冲大小是不确定的,如何能统一且更灵活,就是柔性数组.
可以用下面的代码来测试一下柔性数组
typedef struct tagStudent{int id;int len;char name[];}Student,*PtrStu;PtrStu createStudent(int id, char name[]){PtrStu pStu = (PtrStu)malloc( sizeof(Student) + sizeof(char) * (strlen(name)+1));pStu->id = id;pStu->len = strlen(name);strcpy(pStu->name, name);return pStu;}int main(void){PtrStu pStu1 = createStudent(1001, "Tom");PtrStu pStu2 = createStudent(1002, "Martin");printf("name:%s,id:%d\n", pStu1->name, pStu1->id);printf("name:%s,id:%d\n", pStu2->name, pStu2->id);free(pStu1);free(pStu2);}
