C语言_字节对齐_总结

问大家一个问题:

struct STUDENT
{
char a;
int b;
}data;
如上结构体变量 data 占多少字节?char 占 1 字节,int 占 4 字节,所以总共占 5 字节吗?我们写一个程序验证一下:

#include <stdio.h>
struct STUDENT
{
char a;
int b;
}data;

int main(void)
{
printf("%p, %p\n", &data.a, &data.b); //%p是取地址输出控制符
printf("%d\n", sizeof(data));
return 0;
}
输出结果是:
00427E68, 00427E6C
8

我们看到 data 不是占 5 字节,而是占 8 字节。变量 a 的地址是从 00427E68 到 00427E6B,占 4字 节;变量 b 的地址是从 00427E6C 到 00427E6F,也占 4 字节。b 占 4 字节我们能理解,但 a 是 char 型,char 型不是占 1 字节吗,这里为什么占 4 字节?其实不是它占了 4 字节,它占的还是 1 字节,只不过结构体中有一个字节对齐的概念。

什么叫字节对齐?我们知道结构体是一种构造数据类型,里面可以有不同数据类型的成员。在这些成员中,不同的数据类型所占的内存空间是不同的。那么系统是怎么给结构体变量的成员分配内存的呢?或者说这些成员在内存中是如何存储的呢?通过上面这个例子我们知道肯定不是顺序存储的。

那么到底是怎么存储的呢?就是按字节对齐的方式存储的!即以结构体成员中占内存最多的数据类型所占的字节数为标准,所有的成员在分配内存时都要与这个长度对齐。我们举一个例子:我们以上面这个程序为例,结构体变量 data 的成员中占内存最多的数据类型是 int 型,其占 4 字节的内存空间,那么所有成员在分配内存时都要与 4 字节的长度对齐。也就是说,虽然 char 只占 1 字节,但是为了与 4 字节的长度对齐,它后面的 3 字节都会空着,即:

a 空 空 空
b

所谓空着其实也不是里面真的什么都没有,它就同定义了一个变量但没有初始化一样,里面是一个很小的、负的填充字。为了便于表达,我们就暂且称之为空好了。

如果结构体成员为:

struct STUDENT
{
char a;
char b;
int c;
}data;
那么这三个成员是怎么对齐的?a 和 b 后面都是空 3 字节吗?不是!如果没有 b,那么 a 后面就空 3 字节,有了 b 则 b 就接着 a 后面填充。即:

a b 空 空
c

所以这时候结构体变量 data 仍占 8 字节。我们写一个程序验证一下:

#include <stdio.h>
struct STUDENT
{
char a;
char b;
int c;
}data;

int main(void)
{
printf("%p, %p, %p\n", &data.a, &data.b, &data.c); //%p是取地址输出控制符
printf("%d\n", sizeof(data));
return 0;
}
输出结果是:
00427E68, 00427E69, 00427E6C
8

这时我们发现一个问题:所有成员在分配内存的时候都与 4 字节的长度对齐,多个 char 类型时是依次往后填充,但是 char 型后面的 int 型为什么不紧接着后面填充?为什么要另起一行?也就是说,到底什么时候是接在后面填充,什么时候是另起一行填充?

我们说,所有的成员在分配内存时都要与所有成员中占内存最多的数据类型所占内存空间的字节数对齐。假如这个字节数为 N,那么对齐的原则是:理论上所有成员在分配内存时都是紧接在前一个变量后面依次填充的,但是如果是“以 N 对齐”为原则,那么,如果一行中剩下的空间不足以填充某成员变量,即剩下的空间小于某成员变量的数据类型所占的字节数,则该成员变量在分配内存时另起一行分配。

下面再来举一个例子,大家觉得下面这个结构体变量data占多少字节?

struct STUDENT
{
char a;
char b;
char c;
char d;
char e;
int f;
}data;
首先最长的数据类型占 4 字节,所以是以 4 对齐。然后 a 占 1 字节,b 接在 a 后面占 1 字节,c 接在 b 后面占 1 字节,d 接在 c 后面占 1 字节,此时满 4 字节了,e 再来就要另起一行。f 想紧接着 e 后面分配,但 e 后面还剩 3 字节,小于 int 类型的 4 字节,所以 f 另起一行。即该结构体变量分配内存时如下:

a b c d
e 空 空 空
f

即总共占 12 字节。我们写一个程序验证一下:

#include <stdio.h>
struct STUDENT
{
char a;
char b;
char c;
char d;
char e;
int f;
}data;

int main(void)
{
printf("%p, %p, %p, %p, %p, %p\n", &data.a, &data.b, &data.c, &data.d, &data.e, &data.f); //%p是取地址输出控制符
printf("%d\n", sizeof(data));
return 0;
}
输出结果是:
00427E68, 00427E69, 00427E6A, 00427E6B, 00427E6C, 00427E70
12

现在大家应该能掌握字节对齐的精髓了吧!下面给大家出一个题目试试掌握情况。我们将前面的结构体改一下:

struct STUDENT
{
char a;
int b;
char c;
}data;
即将原来第二个和第三个声明交换了位置,大家看看现在 data 变量占多少字节?没错,是 12 字节。首先最长类型所占字节数为 4,所以是以 4 对齐。分配内存的时候 a 占 1 字节,然后 b 想紧接着 a 后面存储,但 a 后面还剩 3 字节,小于 b 的 4 字节,所以 b 另起一行分配。然后 c 想紧接着 b 后面分配,但是 b 后面没空了,所以 c 另起一行分配。所以总共 12 字节。内存分配图如下所示:

a 空 空 空
b
c 空 空 空

下面写一个程序验证一下:

include <stdio.h>

struct STUDENT
{
char a;
int b;
char c;
}data;

int main(void)
{
printf("%p, %p, %p\n", &data.a, &data.b, &data.c); //%p是取地址输出控制符
printf("%d\n", sizeof(data));
return 0;
}
输出结果是:
00427E68, 00427E6C, 00427E70
12

我们看到,同样三个数据类型,只不过交换了一下位置,结构体变量data所占的内存空间就由8字节变成12字节,多了4字节。这就告诉我们,在声明结构体类型时,各类型成员的前后位置会对该结构体类型定义的结构体变量所占的字节数产生影响。没有规律的定义会增加系统给结构体变量分配的字节数,降低内存分配的效率。但这种影响对操作系统来说几乎是可以忽略不计的!所以我们在写程序的时候,如果有心的话,声明结构体类型时就按成员类型所占字节数从小到大写,或从大到小写。但是如果没有按规律书写的话也不要紧,声明结构体类型时并非一定要从小到大声明,只是为了说明“字节对齐”这个概念!而且有时候为了增强程序的可读性我们就需要没有规律地写,比如存储一个人的信息:

struct STUDENT
{
char name[10];
int age;
char sex;
float score;
}data;
正常的思维是将“性别”放在“年龄”后面,但如果为了内存对齐而交换它们的位置,总让人觉得有点别扭。所以我说“尽量”有规律地写!

这时又有人会提出一个问题:“上面这个结构体变量 data 中有成员 char name[10],长度最长,是 10,那是不是要以 10 对齐?”不是,char a[10] 的本质是 10 个 char 变量,所以就把它当成 10 个 char 变量看就行了。所以结构体变量 data 中成员最长类型占 4 字节,还是以 4 对齐。该结构体变量分配内存时情况如下:

name[0] name[1] name[2] name[3]
name[4] name[5] name[6] name[7]
name[8] name[9] 空 空
age
sex 空 空 空
float

总共 24 字节,我们写一个程序验证一下:

#include <stdio.h>
struct STUDENT
{
char name[10];
int age;
char sex;
float score;
}data;

int main(void)
{
printf("%p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p\n", &data.name[0], &data.name[1], &data.name[2], &data.name[3], &data.name[4], &data.name[5], &data.name[6], &data.name[7], &data.name[8], &data.name[9], &data.age, &data.sex, &data.score);
printf("%d\n", sizeof(data));
return 0;
}
输出结果是:
00427E68, 00427E69, 00427E6A, 00427E6B, 00427E6C, 00427E6D, 00427E6E,
00427E6F, 00427E70, 00427E71, 00427E74, 00427E78, 00427E7C
24

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值