零长度数组概念:
众所周知, GNU/GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展.
多数情况下, 其应用在变长数组中, 其定义如下:
struct Packet
{
int state;
int len;
char cData[0]; //这里的0长结构体就为变长结构体提供了非常好的支持
};
首先对 0长度数组, 也叫柔性数组 做一个解释 :
-
用途 : 长度为0的数组的主要用途是为了满足需要变长度的结构体
-
用法 : 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的. 对于编译器来说, 此时长度为0的数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量
(注意 : 数组名永远都不会是指针!), 但对于这个数组的大小, 我们可以进行动态分配
注意 :如果结构体是通过calloc、malloc或 者new等动态分配方式生成,在不需要时要释放相应的空间。
优点 :比起在结构体中声明一个指针变量、再进行动态分 配的办法,这种方法效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。
缺点 :在结构体中,数组为0的数组必须在最后声明,使 用上有一定限制。
对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可修改的地址常量!
0长度数组的用途:
我们设想这样一个场景, 我们在网络通信过程中使用的数据缓冲区, 缓冲区包括一个len字段和data字段, 分别标识数据的长度和传输的数据, 我们常见的有几种设计思路:
-
定长数据缓冲区, 设置一个足够大小 MAX_LENGTH 的数据缓冲区
-
设置一个指向实际数据的指针, 每次使用时, 按照数据的长度动态的开辟数据缓冲区的空间
我们从实际场景中应用的设计来考虑他们的优劣. 主要考虑的有, 缓冲区空间的开辟, 释放和访问。
1、定长包(开辟空间, 释放, 访问):
比如我要发送 1024 字节的数据, 如果用定长包, 假设定长包的长度 MAX_LENGTH 为 2048, 就会浪费 1024 个字节的空间, 也会造成不必要的流量浪费:
-
数据结构定义:
// 定长缓冲区
struct max_buffer
{
int len;
char data[MAX_LENGTH];
};
-
数据结构大小:考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH
由于考虑到数据的溢出, 变长数据包中的 data 数组长度一般会设置得足够长足以容纳最大的数据, 因此 max_buffer 中的 data 数组很多情况下都没有填满数据, 因此造成了浪费
-
数据包的构造:假如我们要发送 CURR_LENGTH = 1024 个字节, 我们如何构造这个数据包呢;一般来说, 我们会返回一个指向缓冲区数据结构 max_buffer 的指针:
/// 开辟
if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
{
mbuffer->len = CURR_LENGTH;
memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", mbuffer->len, mbuffer->data);
}
-
访问:这段内存要分两部分使用;前部分 4 个字节 p->len, 作为包头(就是多出来的那部分),这个包头是用来描述紧接着包头后面的数据部分的长度,这里是 1024, 所以前四个字节赋值为 1024 (既然我们要构造不定长数据包,那么这个包到底有多长呢,因此,我们就必须通过一个变量来表明这个数据包的长度,这就是len的作用);而紧接其后的内存是真正的数据部分, 通过 p->data, 最后, 进行一个 memcpy() 内存拷贝, 把要发送的数据填入到这段内存当中
-
释放:那么当使用完毕释放数据的空间的时候, 直接释放就可以了
/// 销毁
free(mbuffer);
mbuffer = NULL;
2、小结:
-
使用定长数组, 作为数据缓冲区, 为了避免造成缓冲区溢出, 数组的大小一般设为足够的空间 MAX_LENGTH, 而实际使用过程中, 达到 MAX_LENGTH 长度的数据很少, 那么多数情况下, 缓冲区的大部分空间都是浪费掉的
-
但是使用过程很简单, 数据空间的开辟和释放简单, 无需程序员考虑额外的操作
3、 指针数据包(开辟空间, 释放, 访问):
如果你将上面的长度为 MAX_LENGTH 的定长数组换为指针, 每次使用时动态的开辟 CURR_LENGTH 大小的空间, 那么就避免造成 MAX_LENGTH - CURR_LENGTH 空间的浪费, 只浪费了一个指针域的空间:
-
数据包定义:
struct point_buffer
{
int len;
char *data;
};
-
数据结构大小:考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char *)
-
空间分配:但是也造成了使用在分配内存时,需采用两步
// =====================
// 指针数组 占用-开辟-销毁
// =====================
/// 占用
printf("the length of struct test3:%d\n",sizeof(struct point_buffer));
/// 开辟
if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
pbuffer->len = CURR_LENGTH;
if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH

本文介绍了C语言的零长度数组(柔性数组)的概念及其用途,探讨了其在变长数组、数据缓冲区设计中的应用。通过对比定长数组、指针数据包和变长数据缓冲区,强调了零长度数组在节省空间和提高访问效率方面的优势。同时,讨论了GNU C对变长数组的支持以及0长度数组的其他特征,包括为何不占用存储空间的原因和地址优化的现象。


被折叠的 条评论
为什么被折叠?



