作者:張道遠
链接:https://www.zhihu.com/question/27862634/answer/38506197
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3 个因素导致现在的地址对齐约定:
  1. 生活很艰难
  2. 世界多姿多彩,世上有各种不同的人存在
  3. 但我们还是要在一起呀在一起

以下以最简,理想的模型进行讨论。

计算机的不同组件对“对齐”有不同的看法。比如一个最小存储单位为 8 字节的内存来说。访问地址1, 大小为 4 字节的数据。只需读取地址 0 的 8 字节的数据。然后在出口处移位下就行。因为只需在出口处做一次处理,所以可以不计成本进行移位优化。因为 RISC CPU 的设计,大多精简指令集的指令长就是字长。而指令还需区分取立即数和各种 action, 所以一字长的指令无法全部用来表示地址空间。所以大多 RISC CPU 强制地址对齐,地址的低位脑补成 0。顺便也减少了地址线的宽度。

访问内存的速度是非常非常非常……慢的。再加上 CPU 及其指令设计的限制。在这种艰苦条件下,我们必须无所不用其极地减少随机内存访问次数:

  • 在一个访问周期里读写最多,但不更多的数据。也就是一字长大小的数据。
  • 对于 CPU, 内存的最小存储单元的大小为最大,但不更大的 CPU 字长。

基于此,一般的 RISC CPU 的地址线宽度为 . 比如一个 32 位 CPU 的字长是 32bit, 字节大小为 8bit, 那么地址线宽度为 可选择 个地址单位, 每个单位的大小是 4 字节 于是总共可管理 字节的内存。这也是逻辑地址最低 位总是为 0 的来历了。对于 32bit 字长的 CPU, 就是最低 2 位为 0 了。注意,此段所述只是基于 de-facto 习惯(1 byte = 8 bit, mem 空间大小为 byte), 为了便于讨论而做的假设。并无特别的意义和强行规定。

以一个字长为 4 字节的 RISC CPU 来进行讨论。单位为字节。
此时,如果我们需要访问地址为 2 的大小为 1 字长也就是 4 字节的数据,也就是 2-5 的数据. 地址 2 mod 大小 4 = 2 不为 0. 这是未对齐的访问。(地址线以二进制表示。最后两位空置,所以始终为逻辑 0)我们需要将地址线设为 0(00) 读出 0-4, 取 2-3, 然后将地址线设为 1(00) 读出 4-7 取 4-5, 然后再合成所需数据。共两次访问。如果要访问地址为 0(00) 或者 1(00) 的大小为 1 字长的数据。则一次访问即可。
这就是地址不对齐导致访问变慢的来历了。

此时若要访问 地址 2 的半个字长大小的数据。也就是 2-3 的数据。我们可以将地址设为 0(00) 读出 0-3 的数据,然后将其在寄存器中右移 2 字节即可。

那么,问题来了。既然如此,我也可以访问地址 1, 大小为 2 字节的数据啊。也就是 1-2 的数据。将地址线设成 0(00) 读出数据,然后在寄存器内左移 1 字节,再右移 2 字节就行了啊。
这时候 1 mod 2= 1, 不为 0, 但需要访问内存的次数还是一次。
但世界上有许多地方,那儿的 CPU 字长只有 2 字节。当数据到那些地方去旅行时。那儿的 CPU 访问地址为 1, 大小为 2 字节的数据的时候还是需要两次的。

那么,问题又来了。字长为 4 字节的 CPU, 访问 8 字节长度的数据,这个数据反正始终都要读两次,那么它不对齐也是可以的呀。只要他的地址 mod 4 为 0 就可以了。
但世界上还有些地方,那儿的 CPU 字长是 8 字节的,当数据到那些地方去旅行时。那儿的 CPU 访问大小为 8 字节的数据,若其地址 mod 8 为 0 时,只要读一次就够了。此时读两次就是一种浪费了。

我们的世界是个艰难但又多姿多彩的世界。为了大家的数据都有一个兼容且一致的模型,方便交换,分析。我们郑重做出约定:

大小为 size 的字段,他的结构内偏移 offset 需符合 offset mod size 为 0.
引用的 wikipedia 的第一段就是对这句话的精确表述。

最后,问题又来了。

struct hi {
let: 4 // padding 4
us: 8 // padding 0
play: 1 // padding 1
together: 2 // padding ?
}

together 字段的 padding 是要多少?是的 padding 0 就行了。所以大小是 8 + 8 + 2 + 2= 20
那为什么 gcc 告诉我们应该是 24 呢。

因为我们的世界不是孤单的世界。
数据们可以欢乐地组成团队。

hi group[2];

如果我们不能相互体谅,自私地将最后的 padding 设为 0 的话。
假设第一个 hi 位于地址 0, 那么第二个 hi 就得从地址 20 开始了。此时 us 的地址是 , 而 28 mod 8= 4 不为 0.
如果 hi 的大小为其中最大单元的整数倍也就是 8 * 3= 24 的话。那么 第二个 hi 的 us 字段的地址是 24+8= 32. 而 32 mod 8= 0, 对齐了。所以,最后我们还需要 padding 4 字节。
在这个不孤单的世界里,为了同一类数据能和谐相处。所以我们郑重做出约定:

整个结构的大小必须是其中最大字段大小的整数倍。

于是,不管是在一个数组里没羞没臊地在一起。还是在这个如此多姿多彩各不相同的世界里到处旅行。数据们的美好的生活都可以快速,和谐,一致地进行啦。

最后,若题主有闲,推荐看一下哈佛的 CS101, From NAND to Tetris 课程。从最简单的逻辑门开始,自己动手打造一遍 latch, flip-flop, register,RAM, ALU, CPU, assembler, compiler. 相信到时候你会有更深的体会。

 
====================================华丽的分隔符======================================================
以下是我个人的一点总结,字对齐的原因是为了能够一次读取当前的数据结构,但是由于不明确机器字长或者代码移植到别的机器的原因,字长是不固定,为了使在与字长与该结构相同的CPU一次读取当前值,而采用了对齐的方式,对于最后一个数据结构的对齐主要原因是考虑到数组的关系,所以选择了与最长数据结构的长度对齐。上文解决了困扰我许久的问题,在此表示发自内心的感谢。

转一下关于struct字对齐的原因的更多相关文章

  1. struct内存对齐1:gcc与VC的差别

    struct内存对齐:gcc与VC的差别 内存对齐是编译器为了便于CPU快速访问而采用的一项技术,对于不同的编译器有不同的处理方法. Win32平台下的微软VC编译器在默认情况下采用如下的对齐规则:  ...

  2. struct 字节对齐详解

    一.什么是字节对齐,为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问, ...

  3. 重新学struct,边界对齐,声明……与Union的区别

    在内存中,编译器按照成员列表顺序分别为每个结构体变量成员分配内存,当存储过程中需要满足边界对齐的要求时,编译器会在成员之间留下额外的内存空间. 如果想确认结构体占多少存储空间,则使用关键字sizeof ...

  4. 利用php unpack读取c struct的二进制数据,struct内存对齐引起的一些问题

    c语言代码 #include <stdio.h> struct test{ int a; unsigned char b; int c; }; int main(){ FILE *fp; ...

  5. struct内存对齐

    内存对齐其实是为了在程序运行的时候更快的查找内存而做的一种编译器优化. 我们先看这样一个例子: #include <iostream> using namespace std; struc ...

  6. struct字节对齐原则

    原则1:windows下,k字节基本类型以k字节倍数偏移量对齐,自定义结构体则以结构体中最高p字节基本类型的p字节倍数偏移量对齐,Linux下则以2或4字节对齐; 原则2:整体对齐原则,例如数组结构体 ...

  7. c\c++里struct字节对齐规则

    规则一.: 每个成员变量在其结构体内的偏移量都是成员变量类型的大小的倍数.   规则二: 如果有嵌套结构体,那么内嵌结构体的第一个成员变量在外结构体中的偏移量,是内嵌结构体中那个数据类型大小最大的成员 ...

  8. jQuery dataTables 列不对齐的原因

    如果把 jQuery dataTables 用在初始化时为隐藏的区域中,会发现表头和内容的列是不对齐的. 解决方案: 如果是折叠的,可以加上: $('#myCollapsible').on('show ...

  9. C 语言结构体 struct 及内存对齐

    struct 结构体 对于复杂的数据类型(例如学生.汽车等),C 语言允许我们将多种数据封装到一起,构成新类型. 跟面向对象语言中的对象相比,结构体只能包含成员变量,不支持操作. #include & ...

随机推荐

  1. XObject.java 对象还没写完,希望电脑不会丢失。坏笑,早点见。

    /*面向对象强调的是对象, 面向过程强调的是功能行为,打开行为,关闭行为,执行行为,把多个行为封装成对象执行更强大的功能就是面向对象,是把多个函数, 多 个行为封装在一起,单一的函数执行对象的功能太困 ...

  2. .net乱码问题

    最近在给一个客户做framwwork版本升级,从1.0版本升级到4.0版本,发现最大的问题就是乱码. 在1.0版本下,gb2312编码能够运行得很好,可是升级到4.0后就都是乱码. 随后将webcon ...

  3. HEAD FIRST HTML & CSS学习笔记

    CSS部分 1. border-bottom属性控制元素下边框的外观.   eg: border-bottom:1px solid maroon;  P265 下划线 text-decoration: ...

  4. 学习ios【1】Objective-C 基本语法

    一 了解一下,找参考资料 1.看书学习object-c语法,第一本看的是<objective-c程序设计>. 2.官网:https://developer.apple.com/librar ...

  5. Android开发--TableLayout的应用

    1.简介 TableLayout为表格框架结构

  6. 测试键盘的控制字符对应的ASCII码值

    #include <stdio.h>#include <termio.h>      //终端操作头文件 char getch(void){     struct termio ...

  7. php 图片验证码生成 前后台验证

    自己从前一段时间做了个php小项目,关于生成图片验证码生成和后台的验证,把自己用到的东西总结一下,希望大家在用到相关问题的时候可以有一定的参考性. 首先,php验证码生成. 代码如下: 1.生成图像代 ...

  8. Pycharm快捷方式

    PYCHARM的快捷方式 PyCharm3.0默认快捷键(翻译的)1.编辑(Editing)Ctrl + Space 基本的代码完成(类.方法.属性)Ctrl + Alt + Space 快速导入任意 ...

  9. tomcat mysql 内存溢出的问题

    原因是mysql的密码有问题 解决办法: 具体操作步骤: 关闭 mysql: # service mysqld stop 然后: # mysqld_safe --skip-grant-tables 启 ...

  10. 【编辑器】【Sublime Text】使用笔记

    1.安装 官网下载即可 2.插件 sublime-text - Sublime Text 怎么高亮 Markdown 的文件语法 设置Sublime为VIM模式 如何在sublime 里面设置 ver ...