0字节(长度)数组
在看某些特定代码的时候,也许有人见过这样的东西,某个结构体中有一个数组,其长度为0,而且不出意外,这个0长度数组肯定是结构体最后一个成员。如果你没有见过,也没什么大不了,因为你很快就要见识了,而且可能要比别人还要对其理解和掌握得更好。
程序任务:
写一个程序,实现一个消息缓存区,消息缓存区中可以最多缓存MAX_MSG_CNT条消息,每条消息内容包括:消息发生时间与消息文本,其中消息文本长度不定,有可能很知,也可能非常长,但最长不超过MAX_MSG_LEN字节。
这个任务也非常简单,我们将实现两个版本来做比较。
版本1:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#define MAX_MSG_CNT 128
#define MAX_MSG_SIZE 1024
typedef struct
{
int time; //时间
char txt[MAX_MSG_SIZE + 1]; //消息文本
} msg_t;
typedef struct
{
int cnt; //消息个数
msg_t *msgs[MAX_MSG_CNT]; //消息队列
} msg_buf_t;
字符串长度为0和50之间msg_buf_t *new_msg_buf()
{
return (msg_buf_t *)malloc(sizeof(msg_buf_t));
}
msg_t *new_msg(int tm, const char *txt)
{
msg_t *msg = (msg_t *)malloc(sizeof(msg_t));
msg->time = tm;
strncpy(msg->txt, txt, MAX_MSG_SIZE);
return msg;
}
int add_msg(int tm, const char *txt, msg_buf_t *mb) {
if (mb->cnt < MAX_MSG_CNT)
{
mb->msgs[mb->cnt++] = new_msg(tm, txt);
}
return 0;
}
int main(int argc, char *argv[])
{
msg_buf_t *mb = new_msg_buf();
add_msg(0, "hello, world!", mb);
add_msg(1, "goodbye!", mb);
return 0;
}
版本2:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#define MAX_MSG_CNT 128
#define MAX_MSG_SIZE 1024
typedef struct
{
int time; //时间
char txt[0]; //消息文本
} msg_t;
typedef struct
{
int cnt; //消息个数
int cap; //消息个数上限msg_t *msgs[0];  //消息队列
} msg_buf_t;
msg_buf_t *new_msg_buf(int cap)
{
msg_buf_t *mb = (msg_buf_t *)malloc(sizeof(msg_buf_t) + sizeof(msg_t *) * cap);
mb->cap = cap;
return mb;
}
msg_t *new_msg(int tm, const char *txt)
{
msg_t *msg = (msg_t *)malloc(sizeof(msg_t) + strlen(txt) + 1);
msg->time = tm;
strcpy(msg->txt, txt);
return msg;
}
int add_msg(int tm, const char *txt, msg_buf_t *mb)
{
if (mb->cnt < mb->cap)
{
mb->msgs[mb->cnt++] = new_msg(tm, txt);
}
return 0;
}
int main(int argc, char *argv[])
{
msg_buf_t *mb = new_msg_buf(MAX_MSG_CNT);
add_msg(0, "hello, world!", mb);
add_msg(1, "goodbye!", mb);
return 0;
}
接下来我们来分析差异,先看数据结构:
两个示例中,我们都定义了msg_t 与msg_buf_t 两个数据结构,分别对应消息与消息缓存区。版本1中,消息的文本长度指定为MAX_MSG_SIZE ,消息缓存区的长度指定为MAX_MSG_CNT ,它们都是长度,也就是我们的数据结构是定长的。而在版本2中,消息的文本长度与消息缓存区的长度给的都是0,这正是本文所要讨论的0字节数组。
为了节省篇幅,接下来只重点对msg_t 进行分析(msg_buf_t
的原理一样)。我们先从内存占
可以看到,在版本1中,无论msg 的文本内容为什么,一个msg 实例的内存占用总是sizeof(int) + MAX_
MSG_SIZE + 1,而版本2对应的长度却随txt 内容而不同,是变长的,明显要节省空间。这正是0字组数组的用处之一。
我们接下来对比msg_t 实例的动态创建差异:
从上面可以看出,无论是动态创建还是静态创建,版本1分配对像的过程都要比版本2简单。在动态创建方式中,版本2但能够做到按需而取,因此点麻烦一点也是可以接受的,需要特别注意的是,如果你在版本2中使用版本1的代码为msg_t实例分配内存空间,你将面临严重的内存越界问题。如果不想动态申请内存,版本1的创建方法非常利落,而版本2则比较曲折,先通过字符数组开辟空间,再定义一个msg_t的指针强制指过去,在空间占用上不占优势,同时过程也是有点复杂,使用的时候要尽量小心。
除节省空间外,使用0字节数组的另一个好处是程序的可扩展性与兼容性要好,在涉及协议处理的程序中,以及我们把做共享库的时候,这点尤其重要,。如本例中的msg_buf_t,方案2不仅在内存使用上要节省,而且可扩展性要好,消息缓存的个数不受当前MAX_MSG_CNT 的定义限制,未来要增加消息缓存个数了,不用修改程序。
实际上,本文的编程任务还可以有第三个版本,如:msg_t可以这么定义:
typedef struct
{
int time; //时间
char *txt; //消息文本
} msg_t;
这个版本和版本2一样,在节省内存与保证程序兼容性两点上都比版本1要占优势。与版本2的不足主要有两点:
1 txt也需动态分配,因此创建msg_t实例的时候要调用两次malloc,更加复杂一些。(如果考虑内存分配失败,处理更加麻烦)。
2 msg_t中的数据存放在两块不连续(极低概率连续)的内存上,对而要求内存连续的场合不太适用或者处理其来比较麻烦。如将msg_t写入文件,版本3要写两次,而版本2只写一次。当然,版本3比版本2也有优势,就是其变长内存区域的大小可以重新调整(调用realloc),而版本2则不同,因此更加适合有这种需求的应用场合(如做变长字符串处理)。
总结:
1 0字节数组只能是结构体的最后一个成员。
2 0字节数组的长度是任意的,但一经创建,长度不可更改。
3 0字节数组并不是万能的,在与其适合的场景中(如协议数据包处理)应用才能发挥价值。
通过上面的对比与讲解,相信大家对0字节数组有了相应的认识和理解,并能够很好地将其应用到合适的场合中。
.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。