上次看到一篇面试分享,里面有个朋友说,面试官问了char[0] 相关问题,可是本身没有遇到过,就绕过了这个问题。程序员
我本身在这篇文章下面作了一些回复。面试
如今我想结合我本身的理解,解释一下这个 char[0] C语言柔性数组的问题。数组
0数组顾名思义,就是数组长度定义为0,咱们通常知道数组长度定义至少为1才会给它分配实际的空间,而定义了0的数组是没有任何空间,可是若是像上面的结构体同样在最后一个成员定义为零数组,虽然零数组没有分配的空间,可是它能够看成一个偏移量,由于数组名这个符号自己表明了一个不可修改的地址常量。柔性数组也叫可伸缩性数组,而0数组是一种柔性数组。数据结构
由于在早期没引入0长度数组的时候, 你们是经过定长数组和指针的方式来解决的, 可是定长数组定义了一个足够大的缓冲区, 这样使用方便, 可是每次都形成空间的浪费指针的方式, 要求程序员在释放空间是必须进行屡次的free操做, 而咱们在使用的过程当中每每在函数中返回了指向缓冲区的指针, 咱们并不能保证每一个人都理解并听从咱们的释放方式因此 GNU 就对其进行了0长度数组的扩展. 当使用data[0]的时候, 也就是0长度数组的时候,0长度数组做为数组名, 并不占用存储空间。这样就能够更加高效的利用内存。tcp
在C99以后,也加了相似的扩展,只不过用的是 char payload[]这种形式(因此若是你在编译的时候确实须要用到-pedantic参数,那么你能够将char payload[0]类型改为char payload[], 这样就能够编译经过了,固然你的编译器必须支持C99标准的,若是太古老的编译器,那可能不支持了。ide
0数组的常规使用首先咱们定义一个结构体,再在一个结构体的最后,定义一个长度为0的数组,就可使得这个结构体是可变长的。函数
以下所示:3d
// 0长度数组
struct zero_buffer
{
int len;
char data[0];
};
这个时候 data[0] 只是个数组名, 是不占用存储空间的.指针
这个结构体的大小用sizeof取长度,实际就是它的成员int的长度,data[0]不占用空间。(数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是表明了一个偏移量, 表明一个不可修改的地址常量!)blog
sizeof(struct zero_buffer) = sizeof(int)
printf("zero struct length is:%d int length is:%d\n",sizeof(struct zero_buffer),sizeof(int));
zero struct length is:4 int length is:4
对于0长数组的这个特色,很容易构造出咱们须要的数据结构,如缓冲区,数据包等等。
假设咱们须要设置一条tcp待发送的数据,长度是15,数据内容是"Hello My Friend",这样咱们就能够按照以下去定义了。其中 zbuffer->data 为定义数据的地址,len表示数据的长度。
咱们使用的时候, 只须要开辟一次空间便可。
#define CURR_LENGTH 15
struct zero_buffer *zbuffer = NULL;
// 开辟
if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
{
zbuffer->len = CURR_LENGTH;
memcpy(zbuffer->data, "Hello My Friend", CURR_LENGTH);
printf("%d, %s\n", zbuffer->len, zbuffer->data);
}
释放空间一次释放便可
// 销毁其余方法实现一些不定长数据的传输
free(zbuffer);
zero_buffer = NULL;
除了0数组以外,还有使用定长数组和指针数组实现柔性数组的功能。
定长数组顾名思义,就是在结构体里面有个定长的数组,这个数组大小是按照咱们定义数据最大来进行设置的,为了就是防止数据储存的时候溢出。
// 定长缓冲区
#define MAX_LENGTH 512
struct max_buffer
{
int len;
char data[MAX_LENGTH];
};
不过使用过程当中,好比我要发送 512 字节的数据, 若是用定长包, 假设定长包的最大长度 MAX_LENGTH 为 1024, 那么就会浪费 512 个字节的空间, 也会形成没必要要的流量浪费。若是数组结构对齐放置(这块知识详细能够看我以前的数据对齐的文章) sizeof(struct max_buffer) = sizeof(int)+ sizieof(char) * MAX_LENGTH
通常来讲, 咱们会返回一个指向缓冲区数据结构 max_buffer 的指针.
#define CURR_LENGTH 512
struct max_buffer *mbuffer = NULL;
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;
使用定长数组, 做为数据缓冲区, 为了不形成缓冲区溢出, 数组的大小通常设为足够的空间 MAX_LENGTH, 而实际使用过程当中, 达到 MAX_LENGTH 长度的数据不多, 那么多数状况下, 缓冲区的大部分空间都是浪费掉的.
可是使用过程很简单, 数据空间的开辟和释放简单, 无需程序员考虑额外的操做
它和0数组的区别在于,零数组最后一个结构体元素定义一个data[0],而指针数组就是结构体中须要定义一个指针数组,这里面的指针数组不须要特定在结构体的最后一个元素。
struct point_buffer
{
char *data;
int len;
};
若是结构体加上 _attribute((packed)) 数据对齐修饰,则 sizeof(point_buffer)= sizeof(int) sizeof(char * ),最终计算为12byte。
#define CURR_LENGTH 1024
struct point_buffer *pbuffer = NULL;
if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
pbuffer->len = CURR_LENGTH;
if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
{
memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", pbuffer->len, pbuffer->data);
}
}
分配内存时,需采用两步
首先, 需为结构体分配一块内存空间;
其次,再为结构体中的成员变量分配内存空间.
这样两次分配的内存是不连续的, 须要分别对其进行管理. 当使用长度为的数组时, 则是采用一次分配的原则, 一次性将所需的内存所有分配给它.
相反, 释放时也是同样的.
free(pbuffer->data);
free(pbuffer);
pbuffer = NULL;
使用指针结果做为缓冲区, 只多使用了一个指针大小的空间, 无需使用固定长度的数组, 不会形成空间的大量浪费.
但那是开辟空间时, 须要额外开辟数据域的空间, 施放时候也须要显示释放数据域的空间, 可是实际使用过程当中, 每每在函数中开辟空间, 而后返回给使用者指向 struct point_buffer 的指针, 这时候咱们并不能假定使用者了解咱们开辟的细节, 并按照约定的操做释放空间, 所以使用起来多有不便, 甚至形成内存泄漏
定长数组使用方便, 可是却浪费空间, 指针形式只多使用了一个指针的空间, 不会形成大量空间分浪费, 可是使用起来须要屡次分配, 屡次释放。因此最优解
0数组的优劣以及注意事项优势 :比起在结构体中声明一个指针变量、再进行动态分配的办法,这种方法效率要高。由于在访问数组内容时,不须要间接访问,避免了两次访存。此外,0数组也不会像定长数组会形成必定的内存的浪费。
缺点 :在结构体中,数组为0的数组必须在最后声明,使用上有必定限制。
结语这就是我分享的零数组,若是你们有更好的想法和需求,也欢迎你们加我好友交流分享哈。