当前位置: 首页 > news >正文

【转载】C/C++内存对齐

转载自https://songlee24.github.io/2014/09/20/memory-alignment/

下面是网易的一道笔试题:struct { uint32_t m1; char m2; } varray[2];以下哪些判断一定成立?(多选)

  1. sizeof(varray[0]) == 5
  2. sizeof(varray[0]) == 8
  3. (void*)&(varray[0].m1) < (void*)&(varray[0].m2)
  4. (char*)&varray[0] == (char*)&(varray[0].m1)
  5. (char*)&varray[0] + sizeof(varray[0]) == (char*)&varray[1]
  6. (char*)&(varray[0].m2) + 1 == (char*)&varray[1]
  7. (char*)&(varray[0].m2) + 4 == (char*)&varray[1]

这个题目考查的就是内存对齐的知识点,看完这篇文章你就知道这道题应该选什么了。

 

一、什么是内存对齐

内存对齐(Memory alignment),也叫字节对齐。

现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

举一个简单的例子,uint32_t所占内存空间为 4 bytes,char为 1 byte。如果把它们放在一个结构体中,则所占的内存空间应该是 4 + 1 = 5 bytes 。而事实上,在VS2012和gcc环境下,sizeof 操作的结果都是 8 bytes:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct
{
uint32_t m1; // #include<stdint.h>
char m2;
}varray;

int main()
{
printf("%d\n",sizeof(varray.m1)); // 输出4
printf("%d\n",sizeof(varray.m2)); // 输出1
printf("%d\n",sizeof(varray)); // 输出8
return 0;
}

 

示图:

这里是以4个字节为一个对齐单位。

 

二、为什么要内存对齐

之所以要内存对齐,有两方面的原因:

  • 平台原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。————- 比如,有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐。

  • 性能原因:内存对齐可以提高存取效率。————- 比如,有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。

 

三、对齐的规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。你可以通过预编译命令#pragma pack(n),n=1,2,4,8,16 来改变这一系数,其中 n 就是你要指定的“对齐系数”。

1)概念:

有效对齐值:是 #pragma pack指定值 和 结构体中最长数据类型长度 中较小的那个。有效对齐值也叫对齐单位

注意:VS、VC 默认是#pragma pack(8),而 gcc 默认是#pragma pack(4),并且gcc只支持1,2,4对齐。

2)规则:

  1. 结构体变量的首地址是有效对齐值(对齐单位)的整数倍。

  2. 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。

  3. 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

  4. 结构体内类型相同的连续元素将在连续的空间内,和数组一样。

下面给出几个例子帮助理解(测试环境为VS2012):

例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct
{
int i; // 4个字节
char c1; // 1个字节
char c2; // 1个字节
}x1;

struct
{
char c1; // 1个字节
int i; // 4个字节
char c2; // 1个字节
}x2;

struct
{
char c1; // 1个字节
char c2; // 1个字节
int i; // 4个字节
}x3;

int main()
{
printf("%d\n",sizeof(x1)); // 输出8
printf("%d\n",sizeof(x2)); // 输出12
printf("%d\n",sizeof(x3)); // 输出8
return 0;
}

 

可以看出,上面定义的三个结构体只是交换了成员声明的顺序。由于结构体中最长的数据类型为4个字节,而VS2010默认#pragma pack(8),所以有效对齐值(对齐单位)为 4 bytes。根据前三条规则可以画出以下图:

例二:

1
2
3
4
5
6
7
8
9
10
11
12
struct
{
int a; // 4个字节
char b[6]; // 6个字节
double c; // 8个字节
}st;

int main()
{
printf("%d\n",sizeof(st)); // 输出24
return 0;
}

 

上面结构体中最长的数据类型 double 为 8 个字节,而VS2012中默认#pragma pack(8),所以有效对齐值(对齐单位)为 8 bytes。根据前三条规则可以画出以下图:

其中的字符数组 char b[6] 可以看做6个单独的 char 成员。

 

四、pragma pack(n)

  • 上面说到,不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n),n=1,2,4,8,16 来改变这一对齐系数。

  • #pragma pack(n)是通过改变有效对齐值来改变数据成员在内存中的布局,若你设定的 n 值没有影响或改变有效对齐值,则成员在内存中的布局不会改变。

下面就看看在1、2、4字节对齐的情况下例一、例二的变化:

1字节对齐:#pragma pack(1)

这时的有效对齐值(对齐单位)为 1 字节,则根据对齐规则,可知成员都是连续存储的。

例一中的输出结果会变为 6,6,6,如下图:

例二中输出结果则变为 4 + 6 + 8 = 18:

2字节对齐:#pragma pack(2)

这时的有效对齐值(对齐单位)为 2 字节,则根据对齐规则,可知例一的输出结果会变为 6,8,6,如下图:

例二结构体中最长数据类型 double 为 8 个字节,所以有效对齐值是 2 。此时输出结果还是18,如下图:

4字节对齐:#pragma pack(4)

对于例一,结构体中最长的数据类型 int 是 4 个字节,所以此时的有效对齐值(对齐单位)仍为 4,没有变化,所以输出仍然是 8,12,8。

而在例二中,原来的有效对齐值为 8,现在变成了 4 。所以输出结果变为 20,具体如下图:

对于 8 字节对齐、 16 字节对齐,在这里就不举例了,相信根据对齐规则你可以很容易写出来。需要注意的是,有些编译器,比如gcc,只支持 1,2,4 对齐。





附:答案

相信看到这里,文章开头的那个网易笔试题应该就很容易得出答案了。只需要根据内存对齐把结构体的内存布局图画出来就一目了然了:

所以多选答案应该是 2、4、5、7。

对于这种类型的题目,最好的办法就是根据对齐规则画出对齐后的内存布局图,简单清晰且不容易出错。

转载于:https://www.cnblogs.com/J1ac/p/9039955.html

相关文章:

  • linux运维、架构之路-MHA高可用方案
  • 线索二叉树实例(前序创建,中序遍历)--2018.5.15
  • vuex填坑记录
  • 多版本并发控制
  • Unity4-用户输入
  • Java Web基础教程(二)开发基础
  • ActiveMq启动后,输入网址出现HTTP ERROR: 503错误的问题
  • 好代码是管出来的——浅谈.Net Core的代码管理方法与落地(更新中...)
  • win10应用程序添加到开机启动项的两种解决办法
  • SSL-ZYC 2432 面积最大
  • 剑指offer-用两个栈实现队列
  • 简单介绍帧动画
  • 浮动菜单
  • 2018.5.20
  • Xpath,XQuery,DTD
  • angular2 简述
  • Computed property XXX was assigned to but it has no setter
  • HTTP那些事
  • JavaScript类型识别
  • JavaWeb(学习笔记二)
  • java中具有继承关系的类及其对象初始化顺序
  • spring cloud gateway 源码解析(4)跨域问题处理
  • 搭建gitbook 和 访问权限认证
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 翻译:Hystrix - How To Use
  • 聊一聊前端的监控
  • 前端性能优化--懒加载和预加载
  • 微服务入门【系列视频课程】
  • 一道闭包题引发的思考
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • 自定义函数
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • const的用法,特别是用在函数前面与后面的区别
  • Semaphore
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • #QT(TCP网络编程-服务端)
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (翻译)Quartz官方教程——第一课:Quartz入门
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (每日持续更新)jdk api之FileReader基础、应用、实战
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (十一)手动添加用户和文件的特殊权限
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • .htaccess配置重写url引擎
  • .net 4.0发布后不能正常显示图片问题
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • .NET企业级应用架构设计系列之应用服务器
  • .net实现头像缩放截取功能 -----转载自accp教程网
  • .stream().map与.stream().flatMap的使用
  • @requestBody写与不写的情况
  • @selector(..)警告提示