Linux字节对齐的那些事
最近,我正在进行一个项目,遇到了一个问题。在ARM上运行的ThreadX与DSP通信时采用了消息队列的方式传递消息(最终实现使用了中断和共享内存的方法)。然而,在实际的操作过程中,发现ThreadX经常崩溃。经过排查,发现问题出在传递消息的结构体没有考虑字节对齐的问题上。
我想顺便整理一下关于C语言中字节对齐的问题,并与大家分享。
一、概念
字节对齐与数据在内存中的位置有关。如果一个变量的内存地址恰好是它长度的整数倍,那么它就被称为自然对齐。例如,在32位CPU下,假设一个整型变量的地址为0x00000004,那么它就是自然对齐的。
首先了解什么位、字节、字
名称 | 英文名 | 含义 |
---|---|---|
位 | bit | 1个二进制位称为1个bit |
字节 | Byte | 8个二进制位称为1个Byte |
字 | word | 电脑用来一次性处理事务的一个固定长度 |
字长
一个字的位数,现代电脑的字长通常为16,32, 64位。(一般N位系统的字长是N/8字节。)
不同的CPU一次可以处理的数据位数是不同的,32位CPU可以一次处理32位数据,64位CPU可以一次处理64位数据,这里的位,指的就是字长。
而所谓的字长,我们有时会称为字(word)。在16位的CPU中,一个字刚好为两个字节,而32位CPU中,一个字是四个字节。若以字为单位,向上还有双字(两个字),四字(四个字)。
二、对齐规则
对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐: 数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。联合 :按其包含的长度最大的数据类型对齐。结构体:结构体中每个数据类型都要对齐。
三、如何限制定字节对齐位数?
1. 缺省
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
2. #pragma pack(n)
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。· 使用伪指令#pragma pack (),取消自定义字节对齐方式。
#pragma pack(n) 用来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
- 如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式
- 如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有一个约束条件,如果n大于等于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须是n的倍数。
3. __attribute
另外,还有如下的一种方式:· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。· attribute ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
3. 汇编.align
汇编代码通常用.align来制定字节对齐的位数。
.align:用来指定数据的对齐方式,格式如下:
.align [absexpr1, absexpr2]
以某种对齐方式,在未使用的存储区域填充值. 第一个值表示对齐方式,4, 8,16或 32. 第二个表达式值表示填充的值。
四、为什么要对齐?
操作系统并非一个字节一个字节访问内存,而是按2,4,8这样的字长来访问。因此,当CPU从存储器读数据到寄存器,IO的数据长度通常是字长。如32位系统访问粒度是4字节(bytes), 64位系统的是8字节。当被访问的数据长度为n字节且该数据地址为n字节对齐时,那么操作系统就可以高效地一次定位到数据, 无需多次读取,处理对齐运算等额外操作。数据结构应该尽可能地在自然边界上对齐。如果访问未对齐的内存,CPU需要做两次内存访问。
字节对齐可能带来的隐患:
代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678; unsigned char *p=NULL; unsigned short *p1=NULL; p=&i; *p=0x00; p1=(unsigned short *)(p+1); *p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.
五、举例
例1:os基本数据类型占用的字节数
首先查看操作系统的位数
在64位操作系统下查看基本数据类型占用的字节数:
#include int main() { printf("sizeof(char) = %ld\n", sizeof(char)); printf("sizeof(int) = %ld\n", sizeof(int)); printf("sizeof(float) = %ld\n", sizeof(float)); printf("sizeof(long) = %ld\n", sizeof(long)); printf("sizeof(long long) = %ld\n", sizeof(long long)); printf("sizeof(double) = %ld\n", sizeof(double)); return 0; }

例2:结构体占用的内存大小–默认规则
考虑下面的结构体占用的位数
struct yikou_s { double d; char c; int i; } yikou_t;
执行结果
sizeof(yikou_t) = 16
在内容中各变量位置关系如下:
其中成员C的位置
还受字节序的影响,有的可能在位置8
编译器给我们进行了内存对齐,各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量类型所占用的字节数的倍数, 且结构的大小为该结构中占用最大空间的类型所占用的字节数的倍数。
对于偏移量:变量type n起始地址相对于结构体起始地址的偏移量必须为sizeof(type(n))的倍数结构体大小:必须为成员最大类型字节的倍数
char: 偏移量必须为sizeof(char) 即1的倍数 int: 偏移量必须为sizeof(int) 即4的倍数 float: 偏移量必须为sizeof(float) 即4的倍数 double: 偏移量必须为sizeof(double) 即8的倍数
例3:调整结构体大小
我们将结构体中变量的位置做以下调整:
struct yikou_s { char c; double d; int i; } yikou_t;
执行结果
sizeof(yikou_t) = 24
各变量在内存中布局如下:

当结构体中有嵌套符合成员时,复合成员相对于结构体首地址偏移量是复合成员最宽基本类型大小的整数倍。
例4:#pragma pack(4)
#pragma pack(4) struct yikou_s { char c; double d; int i; } yikou_t; sizeof(yikou_t) = 16
例5:#pragma pack(8)
#pragma pack(8) struct yikou_s { char c; double d; int i; } yikou_t; sizeof(yikou_t) = 24
例6:汇编代码
举例:以下是截取的uboot代码中异常向量irq、fiq的入口位置代码:
六、汇总实力
有手懒的同学,直接贴一个完整的例子给你们:
#include main() { struct A { int a; char b; short c; }; struct B { char b; int a; short c; }; struct AA { // int a; char b; short c; }; struct BB { char b; // int a; short c; }; #pragma pack (2) /*指定按2字节对齐*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定对齐,恢复缺省对齐*/ #pragma pack (1) /*指定按1字节对齐*/ struct D { char b; int a; short c; }; #pragma pack ()/*取消指定对齐,恢复缺省对齐*/ int s1=sizeof(struct A); int s2=sizeof(struct AA); int s3=sizeof(struct B); int s4=sizeof(struct BB); int s5=sizeof(struct C); int s6=sizeof(struct D); printf("%d\n",s1); printf("%d\n",s2); printf("%d\n",s3); printf("%d\n",s4); printf("%d\n",s5); printf("%d\n",s6); }
以上是Linux字节对齐的那些事的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

VS Code 系统要求:操作系统:Windows 10 及以上、macOS 10.12 及以上、Linux 发行版处理器:最低 1.6 GHz,推荐 2.0 GHz 及以上内存:最低 512 MB,推荐 4 GB 及以上存储空间:最低 250 MB,推荐 1 GB 及以上其他要求:稳定网络连接,Xorg/Wayland(Linux)

Linux系统的五个基本组件是:1.内核,2.系统库,3.系统实用程序,4.图形用户界面,5.应用程序。内核管理硬件资源,系统库提供预编译函数,系统实用程序用于系统管理,GUI提供可视化交互,应用程序利用这些组件实现功能。

虽然 Notepad 无法直接运行 Java 代码,但可以通过借助其他工具实现:使用命令行编译器 (javac) 编译代码,生成字节码文件 (filename.class)。使用 Java 解释器 (java) 解释字节码,执行代码并输出结果。

VS Code扩展安装失败的原因可能包括:网络不稳定、权限不足、系统兼容性问题、VS Code版本过旧、杀毒软件或防火墙干扰。通过检查网络连接、权限、日志文件、更新VS Code、禁用安全软件以及重启VS Code或计算机,可以逐步排查和解决问题。

要查看 Git 仓库地址,请执行以下步骤:1. 打开命令行并导航到仓库目录;2. 运行 "git remote -v" 命令;3. 查看输出中的仓库名称及其相应的地址。

VS Code 可以在 Mac 上使用。它具有强大的扩展功能、Git 集成、终端和调试器,同时还提供了丰富的设置选项。但是,对于特别大型项目或专业性较强的开发,VS Code 可能会有性能或功能限制。

Visual Studio Code (VSCode) 是一款跨平台、开源且免费的代码编辑器,由微软开发。它以轻量、可扩展性和对众多编程语言的支持而著称。要安装 VSCode,请访问官方网站下载并运行安装程序。使用 VSCode 时,可以创建新项目、编辑代码、调试代码、导航项目、扩展 VSCode 和管理设置。VSCode 适用于 Windows、macOS 和 Linux,支持多种编程语言,并通过 Marketplace 提供各种扩展。它的优势包括轻量、可扩展性、广泛的语言支持、丰富的功能和版

vscode 内置终端是一个开发工具,允许在编辑器内运行命令和脚本,以简化开发流程。如何使用 vscode 终端:通过快捷键 (Ctrl/Cmd ) 打开终端。输入命令或运行脚本。使用热键 (如 Ctrl L 清除终端)。更改工作目录 (如 cd 命令)。高级功能包括调试模式、代码片段自动补全和交互式命令历史。
