哦!数组还能这么用,学到了!

开发 后端
即结构体的最后一个成员,可以不完整类型(一种缺乏足够的信息去描述一个完整对象的类型)的数组,但它使得整个结构体的大小就像没有这个成员一样。

这个问题源于读者在阅读redis源码时的一个疑问。

先看下面的代码,对于包含动态字符串成员的两个结构体Test0和Test1占用空间分别是多少呢? 

  1. //来源:公众号【编程珠玑】  
  2. //作者:守望先生  
  3. #include<stdio.h>  
  4. struct Test0  
  5.  
  6.     int a;  
  7.     int b;  
  8.     char *c;  
  9. };  
  10. struct Test1  
  11.  
  12.     int a;  
  13.     int b;  
  14.     char c[];  
  15. };  
  16. int main(void)  
  17.  
  18.     printf("sizeof(struct Test0) = %zd\n",sizeof(struct Test0));  
  19.     printf("sizeof(struct Test1) = %zd\n",sizeof(struct Test1));  
  20.     return 0;  

很多读者一眼就能看出来,在64位系统上,编译为64位程序,其输出结果为: 

  1. 16  

对于Test0的结果是16,通常没有什么疑问,毕竟4(int)+4(int)+8(指针)= 16,但是对于后者的结构体占用空间为8字节,有的读者可能会有疑问。(关于字节对齐,参考《字节对齐,看这篇就懂了》)

柔性数组(flexible array)

实际上这是在C99中引入的柔性数组的特性。即结构体的最后一个成员,可以不完整类型(一种缺乏足够的信息去描述一个完整对象的类型)的数组,但它使得整个结构体的大小就像没有这个成员一样。但是呢,当用结构体通过这个名字访问这个成员时,就像访问一个普通数组成员一样。

如果数组最终一个元素都没有的话,那么访问这个数组将会是未定义行为了。

正如我们前面所看到的: 

  1. struct Test1  
  2.  
  3.     int a;  
  4.     int b;  
  5.     char c[];  
  6. }; 

成员c是一个数组,但是并没有指定大小,使用sizeof计算Test1,其占用空间也仅仅是8字节。

有什么好处?

那么使用柔性数组有什么好处呢?

内存申请和释放

假设分别使用两种类型的结构体,存储16字节的字符数据,需要申请内存。对于struct Test0: 

  1. strcut Test0 *t0 = malloc(sizeof(struct Test0));//为结构体申请内存  
  2. t0->c = malloc(sizeof(char) * 16);//为成员指向的数据申请内存 

而对于struct Test1: 

  1. strcut Test1 *t1 = malloc(sizeof(struct Test1) + sizeof(char) * 16); 

看出区别了吗?前者需要两次内存申请,而后者只需要一次。前者地址不连续(两次malloc),后者地址连续。而你访问成员c的时候,只需要下面这样就可以:

t1->c,和普通成员无异。

要判断它们的地址是否连续也非常简单,只需要分别打印b和c的地址就可以了。

和内存释放类似,前面需要单独释放成员c申请的内存,而后者可以一起释放。

数据拷贝

正由于前面的差别,导致数据拷贝时,更有区别。

对于struct Test0: 

  1. //memcpy(t0copy,t0,sizeof(struct Test0));//不可,这样直接t0copy的c和t0的c指向同一片内存区域。  
  2. t0t0copy.a = t0.a;  
  3. t0t0copy.b = t0.b;  
  4. memcpy(t0copy.c,t0.c,sizeof(char)*16); 

这里无法一次拷贝,因为它的成员c是一个指针类型,我们需要的是一份完整拷贝,因此必须拷贝它指向的内存。(参考《结构体成员赋值到底是深拷贝还是浅拷贝?》)

但是对于struct Test1: 

  1. memcpy(t0copy,t0,sizeof(strcut Test1) + sizeof(char) * 16); 

在这里,由于柔性数组的内存,它的数据内容和结构体数据成员的地址是连续的,因此可以直接拷贝。

减少内存碎片

由于结构体的柔性数组和结构体成员的地址是连续的,即可一同申请内存,因此更大程度地避免了内存碎片。另外由于该成员本身不占结构体空间,因此,整体而言,比普通的数组成员占用空间要会稍微小点。

零长数组

与柔性数组功能类似,还有一个0长数组,不过它并不是标准中的,但是它可以实现类似的功能,使用方式如下: 

  1. struct Test1  
  2.  
  3.     int a;  
  4.     int b;  
  5.     char c[0];  
  6. }; 

差别在于使得数组长度为0。但是由于它并非C标准中的,因此从可移植性考虑,不建议使用这种方式,除非你还无法使用C99。

总结

柔性数组的使用:

  •  位于结构体最后一个位置
  •  不完整数组类型
  •  不是唯一成员

最后,放张图,看差别:

普通和柔性数组

 

 

责任编辑:庞桂玉 来源: C语言与C++编程
相关推荐

2017-03-17 13:40:48

思科视频

2022-10-28 19:19:11

ChromeNetwork网络

2021-11-22 11:05:20

Vue 3setup前端

2021-08-29 18:13:03

缓存失效数据

2020-07-21 18:54:21

Rust类型转换语言

2019-03-28 11:07:56

Spring BootRedis缓存

2013-03-26 13:26:41

婚礼微信微信公众账号

2023-10-16 08:55:43

Redisson分布式

2022-02-14 21:58:58

netstatLinuxWindows

2023-04-10 07:40:36

GraphQLRest通信模式

2021-03-09 09:55:02

Vuejs前端代码

2022-10-31 08:47:21

人脸识别按键键盘

2023-06-03 00:05:18

TypeScriptJSDoc扫描器

2022-07-19 08:04:04

HTTP应用层协议

2024-11-13 09:22:40

2010-01-20 10:37:48

Chrome浏览器

2019-12-30 09:51:35

Word设计模式软件

2020-05-09 16:45:56

ping命令Linux

2021-05-12 07:03:25

Switch报空指针

2024-07-31 09:28:56

点赞
收藏

51CTO技术栈公众号