Linux字节对齐的那些事

最近,我正在进行一个项目,遇到了一个问题。在arm上运行的threadx与dsp通信时采用了消息队列的方式传递消息(最终实现使用了中断和共享内存的方法)。然而,在实际的操作过程中,发现threadx经常崩溃。经过排查,发现问题出在传递消息的结构体没有考虑字节对齐的问题上。

我想顺便整理一下关于C语言中字节对齐的问题,并与大家分享。

一、概念

字节对齐与数据在内存中的位置有关。如果一个变量的内存地址恰好是它长度的整数倍,那么它就被称为自然对齐。例如,在32位CPU下,假设一个整型变量的地址为0x00000004,那么它就是自然对齐的。

首先了解什么位、字节、字

名称 英文名 含义

位bit1个二进制位称为1个bit字节Byte8个二进制位称为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基本数据类型占用的字节数

首先查看操作系统的位数Linux字节对齐的那些事

在64位操作系统下查看基本数据类型占用的字节数:

#include int main(){    printf("sizeof(char) = %ld", sizeof(char));    printf("sizeof(int) = %ld", sizeof(int));    printf("sizeof(float) = %ld", sizeof(float));    printf("sizeof(long) = %ld", sizeof(long));                                          printf("sizeof(long long) = %ld", sizeof(long long));    printf("sizeof(double) = %ld", sizeof(double));    return 0;}

登录后复制Linux字节对齐的那些事

例2:结构体占用的内存大小–默认规则

考虑下面的结构体占用的位数

struct yikou_s{    double d;    char c;    int i;} yikou_t;

登录后复制

执行结果

sizeof(yikou_t) = 16

登录后复制

在内容中各变量位置关系如下:

其中成员C的位置Linux字节对齐的那些事还受字节序的影响,有的可能在位置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

登录后复制

各变量在内存中布局如下:

Linux字节对齐的那些事

当结构体中有嵌套符合成员时,复合成员相对于结构体首地址偏移量是复合成员最宽基本类型大小的整数倍。

例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的入口位置代码:Linux字节对齐的那些事

六、汇总实力

有手懒的同学,直接贴一个完整的例子给你们:

#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",s1);printf("%d",s2);printf("%d",s3);printf("%d",s4);printf("%d",s5);printf("%d",s6);}

登录后复制

以上就是Linux字节对齐的那些事的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2205788.html

(0)
上一篇 2025年2月26日 01:41:41
下一篇 2025年2月26日 01:42:08

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • Yum在Linux中如何设置自动更新

    本文介绍如何在Linux系统中利用yum-cron工具实现yum包的自动更新。 步骤一:安装yum-cron 首先,确认系统是否已安装yum-cron。若未安装,请根据您的Linux发行版执行以下命令: 基于RHEL/CentOS系统: s…

    2025年4月1日
    100
  • Compton在Linux中的资源占用情况如何

    Compton是一款轻量级X11窗口管理器,通过合成层优化,有效降低桌面环境渲染负载,提升系统性能。在Linux系统中,Compton的资源占用通常很低,因为它主要负责窗口合成,而非整个桌面的渲染工作。 Compton资源占用分析: CPU…

    2025年4月1日
    100
  • mount命令挂载时遇到权限问题怎么解决

    使用mount命令挂载文件系统时遇到权限问题?这通常是因为当前用户权限不足。以下几种方法可以解决: 超级用户权限: 最直接的方法是使用sudo命令,以root权限执行挂载操作: sudo mount /dev/sdb1 /mnt/mydis…

    2025年4月1日
    100
  • LNMP与MySQL优化:如何提高数据库性能

    提升LNMP(Linux, Nginx, MySQL, PHP)架构下MySQL数据库性能的关键策略: 一、硬件升级 内存扩容: MySQL对内存需求量大,增加服务器内存可显著提升性能。SSD升级: 使用固态硬盘(SSD)替代传统机械硬盘(…

    2025年4月1日
    100
  • Linux mount命令挂载光驱到文件夹

    在Linux系统中,利用mount命令轻松挂载光驱到指定目录。以下步骤将引导您完成整个过程: 创建挂载点: 首先,创建一个用于挂载光驱的目录。例如,创建一个名为/mnt/cdrom的目录: sudo mkdir -p /mnt/cdrom …

    2025年4月1日
    100
  • 如何挂载USB设备到Linux系统

    在linux系统中挂载usb设备通常涉及以下几个步骤: 插入USB设备:将USB设备插入Linux系统的USB端口。 查看设备名称:插入USB设备后,可以使用以下命令来查看系统识别的设备名称: lsblk 登录后复制 或者 sudo fdi…

    互联网 2025年4月1日
    100
  • Yum在Linux中如何查看软件详情

    本文介绍如何在Linux系统中使用yum命令查看软件包信息。 掌握以下yum命令,您可以轻松管理系统软件。 查看所有可用软件包: yum list available 登录后复制 搜索特定软件包: yum search 登录后复制 例如,搜…

    2025年4月1日
    100
  • Linux LAMP如何实现远程访问

    本文将指导您如何在Linux系统上配置LAMP环境并实现远程访问。 以下步骤将确保您的Web服务器安全且可访问。 第一步:安装和配置Apache Web服务器 首先,确保您的Linux系统已安装Apache。使用您的发行版包管理器安装:例如…

    2025年4月1日
    100
  • Linux服务器日志清理策略是什么

    linux服务器日志清理策略主要包括以下几个方面: 日志文件管理 日志轮转(Log Rotation) 使用logrotate工具定期压缩、备份和删除旧日志文件。配置文件通常位于/etc/logrotate.conf或/etc/logrot…

    互联网 2025年4月1日
    100
  • Linux strings命令支持哪些常见的文件格式

    linux strings 命令详解:从二进制文件提取可打印字符串 strings 命令是 Linux 系统中一个强大的工具,用于从各种类型的文件中提取可打印的字符串。这些字符串可能包含重要的信息,例如错误消息、函数名、文件路径等等,对于分…

    2025年4月1日
    100

发表回复

登录后才能评论