C/C++零长度数组的妙用

目录

1.简介

2.用法

3.应用场景

4.例程

5.总结


1.简介

 在 gcc 手册中,有如下一段代码片段:

struct Line {

    int length;

    char contents[0];

};

零长度数组又叫柔性数组,这个非常贴切。它早期是GNU的一个扩展,后来在C99中也进行了支持(-pedantic 禁用扩展),不过在C99中使用时,就不用写那个0了。如果在早期的VC或者其它平台上不支持这种零长度数组的,可以考虑使用一些变通的方式。

2.用法

        长度为的数组在标准 c 和 c++ 中是不允许的,如果使用长度为的数组,编译时会产生错误,提示数组长度不能为。但在 GNUC 中,这种用法却是合法的。它的最典型的用法就是位于数组中的最后一项,如上面所示,这样做主要是为了方便内存缓冲区的管理。如果你将上面的长度为的数组换为指针,那么在分配内存时,需采用两步:首先,需为结构体分配一块内存空间;其次再为结构体中的成员变量分配内存空间。这样两次分配的内存是不连续的,需要分别对其进行管理。当使用长度为的数组时,则是采用一次分配的原则,一次性将所需的内存全部分配给它。相反,释放时也是一样的。

        在结构体中,长度为0的数组不会占用存储空间 ,在上述例子中 sizeof(Line)=4, 在申请内存空间时,缓冲区的空间可以和结构体的空间一起申请,一次操作就可以完成.例如:

uint32_t length = 10;
struct Line *pLine = (struct Line *)malloc(sizeof (struct Line) + length);
pLine->length = length;

上述代码就动态地为结构体申请了长度(length)为10byte的缓冲区,而且由于是同一次malloc操作,缓冲区与结构体的内存地址是连续的。

综上所述,比起在结构体中定义一个指针指向另一片缓冲区地址的做法,使用长度为0的数组有以下好处:

1)指针本身需要占用内存,而长度为0的数组不需要

2)长度为0的数组定义出的缓冲区可以和结构体处在同一片连续地址中,只要一次malloc操作和free操作.如果用指针,需要分别申请和释放结构体内存和指针指向的内存块,至少需要两次以上的内存操作;其实就是分配一段连续的的内存,减少内存的碎片化。

3.应用场景

        零长度数组主要用来对一些不确定长度的通信负载的内容进行接收。在C/C++的编码过程中,有一个小的技巧,即可以把char指针强转到结构体的指针,这样就可以直接操作结构体。这对于网络或者串口等通信中有着很强的优势(需要处理一下字节对齐)。 那么有人会问为什么不使用指针呢?或者给一个很长的固定数组。首先,给定一个很长的数组,到底多长是很长?如果能确定最多不过一百个,那么用一百个长度来取代零长度数组可能会更好,但可能跨度很大呢,从几个字节长度到几兆字节长度呢?有人可能说,谁会设计这种协议呢?可有的时候儿协议不是自己设计的,用别人的怎么办?那么这时候儿用零长度指针就非常好用。 那为什么不用指针呢?学习过编译相关知识的都知道,指针指向的地址空间,一般情况下是不会和指针当前的地址连续的,以上面的Data结构体为例,如果创建一个对象,使用零长度数组,所有的字节其实是从变量a依次按顺序向下排列的,是线性的。但如果把buf定义成一个指针,那么buf本身这个地址是排在最后的,但对应的指针指向的空间,则指向了另外一个空间地址。表面上创建了一个Data对象,但其是有两块地址空间被分配,这两块地址空间并不连续。不连续意味着什么?意味着上面提到的将char强转成指定的结构体指针成为了不可能。因为buf得到的地址空间,未必是原来定义的空间,这个在硬件通信中非常明显。
        最好的方法就是,直接传输一大段数据,这些数据是连续的存储在一起的,那么通过强转就可直接进行数据操作,可数据长度又是未知的,而普通的数组长度必须是提前指定的,这就是一个矛盾,所以零长度数组的优势就体现出来了。它其实相当于一个占位符,告诉空间分配者,这后面还有一大堆的数据,至于多长,根据实际情况来定。零长度数组等于是取了个巧,既符合了标准中的数组长度必须提前固定,又实现了指针的变长分配。

4.例程

下面看一个模拟的例程:

struct DataWater {
  int count;
  float size;
  char buf[0];
};
char dbuf[1024] = {0};
void testZeroArray(char *data) {
  DataWater *dw;
  dw = (DataWater *)data;
  std::cout << dw->count << std::endl;
}
int main() {
  testZeroArray(dbuf);
  return 0;
}

假如testZeroArray函数是一个接收串口通信或者网络通信的协议结构体,而data就是通过接收得来的一个字节流,那么这个函数的代码就可以直接解析出来协议的内容并为程序所用。

5.总结

        其实零长度数组是一个技巧,这样的小技巧在C/C++中有很多。它们在一些特定的场景下有着非常好的应用,不过前面的定语一定要记清楚,特定的场景。这也是C/C++让初学者感到难缠的一个重要原因。到处都有一些特殊情况,而特殊情况里可能又套着特殊情况。
        这种小技巧的东西,不用刻意学习,用到了,想起来有,查查资料用就可以了。

  • 32
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值