最近一口君在做一个项目,遇到一个问题,ARM上的threadx在与DSP通信采用消息队列的方式传递消息(最终实现原理是中断+共享内存的方式),在实际操作过程中发现threadx总是crash,于是经过排查,是因为传递消息的结构体没有考虑字节对齐的问题。

随手整理一下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字节对齐就是说变量存放的起始地址的偏移量有两种情况:

  1. 如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式
  2. 如果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 <stdio.h>

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 <stdio.h>
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字节对齐的那些事的更多相关文章

  1. linux下字节对齐

    一,内存地址对齐的概念    计算机内存中排列.访问数据的一种方式,包含基本数据对齐和结构体数据对齐.    32位系统中,数据总线宽度为32,每次能够读取4字节数据.地址总线为32,最大寻址空间为4 ...

  2. x86_64 Linux 运行时栈的字节对齐

    前言 C语言的过程调用机制(即函数之间的调用)的一个关键特性(起始大多数编程语言也是如此)都是使用了栈数据结构提供的后进先出的内存管理原则.每一个函数的栈空间被称为栈帧,一个栈帧上包含了保存的寄存器. ...

  3. 【Linux】C字节对齐

    原文地址:https://www.jianshu.com/p/e8fcc01041a7 什么是对齐,以及为什么要对齐: 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问 ...

  4. C语言:内存字节对齐详解[转载]

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

  5. C ~ C语言字节对齐

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

  6. C语言深入学习系列 - 字节对齐&内存管理

    用C语言写程序时需要知道是大端模式还是小端模式. 所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中:所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高 ...

  7. 关于arm处理器 内存编址模式 与 字节对齐方式 (转)

    转自:http://bavon.bokee.com/5429805.html 在x86+Linux上写的程序,在PC机上运行得很好.可是使用ARM的gcc进行交叉编译,再送到DaVinci目标板上运行 ...

  8. c语言,内存字节对齐

    引用:内存字节对齐 写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?讲讲字节对齐吧. /************* ...

  9. C++ 虚基类表指针字节对齐

    下面博客转载自别人的,我也是被这个问题坑了快两天了,关于各种虚基类,虚继承,虚函数以及数据成员等引发的一系列内存对齐的问题再次详细描述 先看下面这片代码.在这里我使用了一个空类K,不要被这个东西所迷惑 ...

  10. struct字节对齐原则

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

随机推荐

  1. 用cvCvtColor转化RGB彩色图像为灰度图像时发生的小失误

    版本信息 MAC版本:10.10.5 Xcode版本:7.2 openCV版本:2.4.13 在运行程序的时候发现cvCvtColor的地方程序报错 error: (-215) src.depth() ...

  2. 【冷启动#1】实用的MySQL基础

    简单安装一下MySQL Windows下(5.7.x) 本体安装 1.首先先下载安装包,名字如下: mysql-5.7.19-winx64.zip 2.配置环境变量,将解压之后的bin目录添加一下 3 ...

  3. python爬虫-request模块

    1. requests 中的请求方法 HTTP 请求方法: requests.get(url, params=None, **kwargs) # GET 请求 requests.post(url, d ...

  4. power bi 如何删除敏感度标签

    经验证,此方法不够彻底,我的office excel打开后还是要添加敏感度标签,即使我把敏感度标签删掉也不行. 当我把创建敏感度标签的管理员账户删掉之后,虽然打开excel还是会显示敏感度标签,但是已 ...

  5. 网络化的控制系统简介(NCS)

    作为一个搞物联网应用的,最近才接触到NCS这个概念.有点东西,赶紧记录一下. NCS由通过网络连通的控制环路组成,控制信号和反馈信号会在控制器和本地系统间交换.典型的方块图如下: 其中,本地(Plan ...

  6. VulnHub - breach系列:breach-2.1

    VulnHub - breach系列:breach-2.1 靶机描述 Breach 2.0 是多部分系列中的第二部分,是一项 boot2root/CTF 挑战,旨在展示真实场景,沿途充满了曲折和恶作剧 ...

  7. Java-EL表达式替换和简化jsp页面中java代码的编写

    概念:Expression Language 表达式语言 作用:替换和简化jsp页面中java代码的编写 语法:$ 注意: jsp默认支持el表达式,如果要忽略el表达式 设置jsp中page指令中: ...

  8. 2022 开源之夏 | Curve 邀你与中国存储软件共成长,赢万元奖金

    Curve 社区携手开源之夏,邀你开展有趣而精彩的开源之旅,直面社区大咖,积累项目经历,摘取丰厚奖金,共同推进我国基础软件自主创新 2022 年,Curve 社区再次加入系列高校开源活动 -- 开源之 ...

  9. [oeasy]python0092_homebrew_家酿俱乐部_比尔盖茨_保罗艾伦

    编码进化 个人电脑 intel 8080 的出现 让 人人都 可能有 一台计算机 Ed Robert 的 创业之路 从 售卖 diy 组装配件 到进军 计算器市场 计算器 毕竟不是 个人计算机 这计算 ...

  10. EasyDesktop 浏览器书签管理从未如此简单

    作为一名软件开发从业人员, 每天80%的时间都在与浏览器打交道, 一半的时间在用浏览器开发调试, 另一半时间则是在互联网上搜寻各种知识和资源. 为此, 我的浏览器书签栏存储和很多非常棒的链接, 多到2 ...