关于零长度数组的思考

首先看一下以下的结构声明

struct Packet
{
    int cmd;
    int len;
    char body[0];
};

可以看到body被声明为一个长度为0的字符数组。经过测试,sizeof(Packet)的值为8,也就是说body实际上并没有分配内存。这种数组被称作零长度数组(Arrays of Length Zero)或者柔性数组,其中char body[0];也可以写为char body[];

那引入零长度数组目的是什么呢?

通常在网络通信的过程总,缓冲区收发数据可能使用定长缓冲区,比如下面的声明:

struct Packet
{
    int cmd;
    int len;
    char body[1024];
};

如此一来数据包长度固定为8+1024,这么做会对程序提出要求,即为了防止溢出,数据不能超过1024个字节。另外一个缺点在于,如果是收发body较小的比如只有10个字节的数据包时,实际上就会有1014个无意义的数据参与了传输,这就造成了资源的浪费。

有没有一种理想的情况,每一次参与网络传输的数据包的大小都恰到好处,从而不浪费额外的空间?

我们想到了使用指针来动态申请内存,比如这样:

struct Packet
{
    int cmd;
    int len;
    char * body;
};

在使用时对body分配内存

Packet packet;
packet.cmd = Login;
packet.len = sizeof(buffer);
packet.body = new char[packet.len];
memcpy(packet.body,buffer,packet.len);

如此一来似乎确实能够满足数据包不定长数据包的需求,但是这种方式下,pakcet的内存和packet.body内存不能保证是连续的,而且需要手动管理packet.body的内存。

为了解决这些痛点,C99标准引入了一个新的特性:柔性数组。

定义如下:

struct test {
    int len;  	// 必须至少有一个其它成员
    char data[]; 	// 柔性数组必须是结构体最后一个成员(也可是其它类型)
};//size = 4

通过一个例子来看看柔性数组如何读写数据。

#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>

//定义一个柔性数组
struct Packet
{
    int len;			//必须至少有一个其它成员
    char body[0];	//柔性数组必须是结构体最后一个成员
};

int main()
{
    printf("sizeof Packet = %lu\n", sizeof(Packet));

    char buffer[]="hello world!";

    //分配一整块内存
    auto * packet = (Packet*) malloc(sizeof(Packet) + sizeof(buffer));
    if (packet == NULL)
    {
        printf("malloc error:%s\n", strerror(errno));
        return -1;
    }

    //写入数据到柔性数组
    packet->len = sizeof (buffer);
    //buffer的数据复制到packet->body
    memcpy(packet->body,buffer,sizeof (buffer));

    printf("sizeof(*packet) = %lu\n", sizeof(*packet));
    printf("sizeof(packet) = %lu\n", sizeof(packet));
    printf("sizeof(packet->body) = %lu\n", sizeof(packet->body));

    //读取数据
    char buffer2[packet->len];
    memcpy(buffer2,packet->body,packet->len);
    printf("packet.body = %s\n", packet->body);
    printf("buffer2 = %s\n", buffer2);

    //销毁柔性数组
    free(packet);
    packet = NULL;
    return 0;
}

运行这份代码,结果如下

sizeof Packet = 4
sizeof(*packet) = 4
sizeof(packet) = 8
sizeof(packet->body) = 0
packet.body = hello world!
buffer2 = hello world!

到此似乎已经能够了解这个零长度数组或者说柔性数组是个什么东西了,接下来看一下内存布局印证一下心中猜想。

可以看到,0x0000600002960000为packet的地址,0x0000600002960000开始的四个字节存储的是0d000000,即十进制13的小端表示,这也就是buffer数据的长度,其后紧跟body数组的内存。

类似于定义以下结构体,但在分配内存的时候多分配一些用来存放buffer,多分配的长度根据buffer的长度确定。巧妙之处在于body不占据结构体的内存,相当于仅仅是一个偏移量,可以轻松的根据body找到多分配的那一串内存。


struct Packet
{
    int len;
};

通过以上的实验,突然有了一个新的想法,直接去掉char body[0];这行,然后直接在该结构体的尾部分配一块内存存放buffer内容:


#include <cstdio>
#include <cstdlib>
#include <cstring>

//定义一个柔性数组
struct Packet
{
    int len;
		//    char body[0];
};

int main()
{
    printf("sizeof Packet = %lu\n", sizeof(Packet));

    char buffer[]="hello world!";

    //分配一整块内存
    auto * packet = (Packet*) malloc(sizeof(Packet) + sizeof(buffer));

    //写入数据到柔性数组
    packet->len = sizeof (buffer);
    memcpy(packet+(int)sizeof(*packet),buffer,sizeof (buffer));

    //读取数据
    char buffer2[packet->len];
    memcpy(buffer2,packet + (int) sizeof(*packet),packet->len);
    printf("packet.body = %s\n", (char * )(packet+(int)sizeof(*packet)));
    printf("buffer2 = %s\n", buffer2);

    //销毁柔性数组
    free(packet);
    packet = NULL;
    return 0;
}
posted @ 2024-04-29 17:54  料峭春风吹酒醒  阅读(6)  评论(0编辑  收藏  举报