哦!数组还能这么用,学到了!
来源:公众号【编程珠玑】
作者:守望先生
ID:shouwangxiansheng
这个问题源于读者在阅读redis源码时的一个疑问。
先看下面的代码,对于包含动态字符串成员的两个结构体Test0和Test1占用空间分别是多少呢?
//来源:公众号【编程珠玑】
//作者:守望先生
#include<stdio.h>
struct Test0
{
int a;
int b;
char *c;
};
struct Test1
{
int a;
int b;
char c[];
};
int main(void)
{
printf("sizeof(struct Test0) = %zdn",sizeof(struct Test0));
printf("sizeof(struct Test1) = %zdn",sizeof(struct Test1));
return 0;
}
很多读者一眼就能看出来,在64位系统上,编译为64位程序,其输出结果为:
16
8
对于Test0的结果是16,通常没有什么疑问,毕竟4(int)+4(int)+8(指针)= 16,但是对于后者的结构体占用空间为8字节,有的读者可能会有疑问。(关于字节对齐,参考《字节对齐,看这篇就懂了》)
柔性数组(flexible array)
实际上这是在C99中引入的柔性数组的特性。即结构体的最后一个成员,可以不完整类型(一种缺乏足够的信息去描述一个完整对象的类型)的数组,但它使得整个结构体的大小就像没有这个成员一样。但是呢,当用结构体通过这个名字访问这个成员时,就像访问一个普通数组成员一样。
如果数组最终一个元素都没有的话,那么访问这个数组将会是未定义行为了。
正如我们前面所看到的:
struct Test1
{
int a;
int b;
char c[];
};
成员c是一个数组,但是并没有指定大小,使用sizeof计算Test1,其占用空间也仅仅是8字节。
有什么好处?
那么使用柔性数组有什么好处呢?
内存申请和释放
假设分别使用两种类型的结构体,存储16字节的字符数据,需要申请内存。对于struct Test0:
strcut Test0 *t0 = malloc(sizeof(struct Test0));//为结构体申请内存
t0->c = malloc(sizeof(char) * 16);//为成员指向的数据申请内存
而对于struct Test1:
strcut Test1 *t1 = malloc(sizeof(struct Test1) + sizeof(char) * 16);
看出区别了吗?前者需要两次内存申请,而后者只需要一次。前者地址不连续(两次malloc),后者地址连续。而你访问成员c的时候,只需要下面这样就可以:
t1->c,和普通成员无异。
要判断它们的地址是否连续也非常简单,只需要分别打印b和c的地址就可以了。
和内存释放类似,前面需要单独释放成员c申请的内存,而后者可以一起释放。
数据拷贝
正由于前面的差别,导致数据拷贝时,更有区别。 对于struct Test0:
//memcpy(t0copy,t0,sizeof(struct Test0));//不可,这样直接t0copy的c和t0的c指向同一片内存区域。
t0copy.a = t0.a;
t0copy.b = t0.b;
memcpy(t0copy.c,t0.c,sizeof(char)*16);
这里无法一次拷贝,因为它的成员c是一个指针类型,我们需要的是一份完整拷贝,因此必须拷贝它指向的内存。(参考《结构体成员赋值到底是深拷贝还是浅拷贝?》)
但是对于struct Test1:
memcpy(t0copy,t0,sizeof(strcut Test1) + sizeof(char) * 16);
在这里,由于柔性数组的内存,它的数据内容和结构体数据成员的地址是连续的,因此可以直接拷贝。
减少内存碎片
由于结构体的柔性数组和结构体成员的地址是连续的,即可一同申请内存,因此更大程度地避免了内存碎片。另外由于该成员本身不占结构体空间,因此,整体而言,比普通的数组成员占用空间要会稍微小点。
零长数组
与柔性数组功能类似,还有一个0长数组,不过它并不是标准中的,但是它可以实现类似的功能,使用方式如下:
struct Test1
{
int a;
int b;
char c[0];
};
差别在于使得数组长度为0。但是由于它并非C标准中的,因此从可移植性考虑,不建议使用这种方式,除非你还无法使用C99。
总结
柔性数组的使用:
- 位于结构体最后一个位置
- 不完整数组类型
- 不是唯一成员
最后,放张图,看差别:
普通和柔性数组
- 解决在控制层springmvc框架发出的400状态的错误
- 解决springmvc在单纯返回一个字符串对象时所出现的乱码情况(极速版)
- MySQL日志文件之错误日志和慢查询日志详解
- 采用HTML5之"data-"机制自由提供数据
- Spring和SpringMVC父子的容器之道---[上篇]
- 快速完成(图片旋转,查看原图)
- Jsp中格式化时间戳的常用标签
- 反射+自定义注解---实现Excel数据列属性和JavaBean属性的自动映射
- 后台模板管理系统___左侧菜单数据的异步加载
- Shiro眼皮下玩ajax,玩出302 Found
- 对于JSONObject,我只是临时抱佛脚
- 总结切面编程AOP的注解式开发和XML式开发
- SpringMVC注解@RequestMapping之produces属性导致的406错误
- SpringBoot集成MyBatis的分页插件PageHelper(回头草)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- threadlocal记录
- 2020-09-22:已知两个数的最大公约数,如何...
- 【超详细】分布式一致性协议 - Paxos
- MySQL 8.0新特性 — 降序索引
- TRTC横竖屏切换2,重力感应
- Elasticsearch 日志配置详解【技术创作101训练营】
- HashMap源码分析
- SpringBoot分组校验及自定义校验注解
- BERT详解
- 技术创作101训练营--一篇文章带你了解CSS基本用法和选择器知识
- 不可不知的 Java 序列化 | 技术创作101训练营
- 当我们做后仿时我们究竟在仿些什么(四)
- Elasticsearch: Rare Terms Aggregation
- Elasticsearch: 运用 Pinned query 来提高文档的排名 (7.5发行版新功能)
- leetcode树之从翻转二叉树