C语言字节对齐及__attribute__((aligned(n))) 与 #pragma(pack(n))的作用

2023-11-03

一:对齐规则

在没有__attribute__((aligned(n)))或#pragma pack修饰的声明下,字节对齐遵循下面三个原则:

1:结构体(struct)的数据成员,第一个数据成员存放的地址为结构体变量偏移量为0的地址处,结构体变量的首地址能够被其最宽基本类型成员的大小所整除。

数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。

2:其他结构体成员自身对齐时,存放的地址为min{自身对齐值, 指定对齐值} 的最小整数倍的地址处.
注:自身对齐值:结构体变量里每个成员的自身大小
注:指定对齐值:有宏 #pragma pack(N) 指定的值,这里面的 N一定是2的幂次方.如1,2,4,8,16等.如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8;__attribute__((aligned(n)))作用在结构体总体大小上,不影响结构体内部成员的排序。

备注:结构体A中嵌套结构体B时,结构体B的地址按照B内成员最大宽度来对齐。计算大小时结构体B在结构体A中展开。结构体作为成员,如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)

3:结构体的总大小,也就是sizeof的结果,总体对齐时,字节大小是min{所有成员中自身对齐值最大的, 指定对齐值} 的整数倍.不足的要补齐。__attribute__((aligned(n)))指定是最小对齐值,n < min{所有成员中自身对齐值最大的, 指定对齐值}时忽略,n > min{所有成员中自身对齐值最大的, 指定对齐值}时,总大小与n对齐。

二:测试样例

下面以32位处理器来看几个例子:

测试1:

struct test
{
  char a;  //1
  char b;  //1
  char c;  //1
};

32位系统下,自然对齐,sizeof大小是3字节。
测试2:

struct test
{
  char a;  //1
  short b;  //2
  char c;  //1
};

32位系统下,自然对齐,sizeof大小是6字节。

来结合对齐规则来看一下,

1、第一个成员首地址为0(准确说是偏移量),这个没什么好说,

2、每个成员的首地址是自身大小的整数倍,因为b是short类型的,占用两个字节,所以,必须以2字节对齐,也就是说你可以把b放在0啊,2啊,4啊这些地址,但是你不能放在1,3,5这样的地址。a的地址是0,下一个地址是1,不能放,只能空掉,放在2位置处。这样,a和b就占了4个字节了,接下来c占一个字节。

3,看第三条规则,结构体的总大小,为其成员中所含最大类型的整数倍。所以,在这个例子中,结构体总大小应该要为2的整数倍,所以是6,

测试3:

struct test
{
  char a;   //1+3
  int  b;    //4
  short c;    //2+2
};

32位系统下,自然对齐,sizeof大小是12字节。
测试4:

struct test
{
  char d[7]; 
  double a;
  short  b;
  char* c;
};

32位系统下,自然对齐,sizeof大小是24字节。

指针在32位系统中都是占4个字节


typedef struct bb
{
    int id;             //[0]....[3]
    double weight;      //[8].....[15]      原则1
    float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3
}BB;

typedef struct aa
{
    char name[2];     //[0],[1]
    int  id;         //[4]...[7]          原则1
    double score;     //[8]....[15]    
    short grade;    //[16],[17]        
    BB b;             //[24]......[47]          原则2
}AA;


int main(void)
{
	AA a;
	
	printf("a size %d\n",sizeof(a));

	return 0;
}

输出结果:a size 48

32位系统下,自然对齐,sizeof大小是48字节。

三、为什么要字节对齐


        需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误。
  
        对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
  
        数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。 
        联合 :按其包含的长度最大的数据类型对齐。 
        结构体: 结构体中每个数据类型都要对齐。

        在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐。即使看起来本来就自然对齐的也要使其对齐,以免不同的编译器生成的代码不一样.

四:__attribute__选项

1. __attribute__ 是什么


__attribute__是GCC里的编译参数,用法有很多种,感兴趣可以阅读一下gcc的相关文档。这里说一下__attribute__对变量和结构体对齐的影响。这里的影响大概分为两个方面,对齐和本身占用的字节数的大小,即sizeof(变量)的值。

2,__attribute__在字节对齐上的使用

__attribute__((packed)):取消对其原则,按照紧密对齐(也就是1字节对齐)

__attribute__((__aligned__(n))):n可以为1/2/4/8等,按照n字节对齐。

正确理解__attribute__((aligned(x))):

__attribute__((__aligned__(n)))定义的是最小对齐边界,它可以用来修饰structure也可以用来修饰变量或者structure成员变量,它只能增加 structure、变量或者structure成员变量的对齐值(GCC手册说在修饰 typedef 类型时可以增加或减少对齐值)。

3. __attribute__((__aligned__(n)))修饰变量

使用的语法如下:
int a __attribute__((__aligned__(8))) = 10;
这个修饰的影响主要是对齐,所谓对齐是存储为值的起始地址。变量a的地址&a,本来是4字节对齐,变成了8字节对齐(有的环境对最大对齐数值有限制)。8字节对齐就是&a的地址是8的整数倍。
sizeof(a) = 4; //a 占用的字节数还是4个字节

下面看看在使用了typedef之后对对齐和变量大小的影响:
typedef int int32_t __attribute__((__aligned__(8))) ;
sizeof(int32_t ) = 4; //占用的字节数还是4个字节
这样说明int32_t 声明的变量按照8字节对齐,大小是4字节,这样就会有一个问题,这个变量不能定义数组:
int32_t array[2]; //这样定义编译器会报err

报错的原因是数组的存储在内存中是连续的,而int32_t 只有4字节确要8字节对齐,这样对齐和连续就不能同时保证,就会报错。

4. __attribute__((__aligned__(n)))修饰结构体

定义一个结构体:

typedef struct flag {
	int a;
	char b;
} FLG;
FLG test;

在32位系统下,自然对齐的情况下:sizeof(test) = 8;

 注意__attribute__((__aligned__(n)))的位置。

typedef struct flag {
	int a;
	char b;
}__attribute__((__aligned__(32))) FLG;
FLG test;

sizeof(test) = 32; 
__attribute__((__aligned__(32)))修饰struct flag,结构体按照32字节对齐



typedef struct flag {
	int a;
	char b;
}__attribute__((__aligned__(1))) FLG;
FLG test;

sizeof(test) = 8; 
__attribute__((__aligned__(1)))修饰struct flag,结构体最小对齐值为1,系统默认4字节对齐了结构体成员,1 < 4,忽略1字节对齐。



typedef struct flag {
	int a;
	char b;
}FLG __attribute__((__aligned__(32)));
FLG test;

sizeof(test) = 8; 
__attribute__((__aligned__(32)))修饰FLG变量,只影响对齐,不影响结构的大小,FLG的地址是32字节对齐。

五:#pragma pack(n)选项

正确理解 #pragma(pack(n))
#pragma(pack(n))定义的是最大对齐边界,它可以用来修饰structure也可以用来修饰structure成员变量,它可以增加/减少 structure或者structure成员变量的对齐值。

1,#pragma pack(n)的使用

#pragma pack需要成对使用,

#pragma pack(1)按照1字节对齐。

#pragma pack()取消定义的对齐规则恢复系统默认。

#pragma pack(1)
struct test
{
  char a;
  int  b;
};
#pragma pack

根据我们之前的分析,这个结构体应该要占用8个字节,但是因为加了#pragma pack(1)导致整个结构体按照1字节来对齐,所以sizeof结果是5.

#pragma pack(2)
struct test
{
  char a;
  int  b;
};
#pragma pack

使用#pragma pack(2)整个结构体按照2字节来对齐,所以sizeof结果是6.

六:#pragma pack(n)与__attribute__((aligned(x)))的区别


#pragma pack(n) 的作用范围是整个文件,也就是说结构体本身和结构体成员都需要遵守这个对齐规则;

__attribute__((aligned(x)))的作用范围如果是用在结构体变量上,则对齐规则只限于结构体地址本身,结构体大小不受影响,其内部的成员变量不遵循此规则。

__attribute__((aligned(x)))的作用范围如果是用在结构体上,则对齐规则只限于结构体本身,其内部的成员变量不遵循此规则。

__attribute__((aligned(x)))如果是修饰结构体中的成员变量,则此成员变量需要遵守对齐规则,最后结构体进行对齐时需要和成员变量的最大对齐值成倍数即可(不一定受此规则限制)。

测试样例1:

#pragma pack(push,2)
typedef struct test{
    char a;
    double b;
    char c;
    int d;
}  __attribute__ ((aligned (32))) test ;
#pragma pack(pop)


① #pragma pack(push,2) 将编译器的最大对齐边界设置为 2
② 以 2 为最大对齐边界后,test 的内存对齐情况(先不考虑 aligned 属性):
typedef struct test{
    char a;   // 1B
    double b; //因为最大对齐是2,所以只需要满足2的倍数,不需要满足8的倍数
    		  //填充 1B ,b 本身占据 8B	
    char c;   //对齐时我们要选择最小的对齐值,对于 c最小的对齐值是1(不是2)
              //无填充,自身占据 1B
    int d;    //对齐值为2,故填充 1B,自身占据 4B
} test ;      //整个结构体对齐值为 2(不是8,因为已经设置了最大对齐值是2)
			  //(1B)+(1B+8B)+(1B)+(1B+4B)+0B(已经满足2的倍数,无需对结构体进行填充)
也就是说当前(不考虑 aligned 属性)时,test 类型占据 16B,最大对齐值 2 。
③ 最后来看 aligned 属性,我们前面说了,aligned 属性设置的是最小对齐值,也就是说当前类型或变量的对齐值要大于或等于aligned 属性设置的对齐值,所以编译器重新设置 test类型的对齐值为32,则test struct需要填充16B。

✔结果就是32B 。
测试样例2:

#pragma pack(push,4)
typedef struct  test{
    char a;
    double b;
    char c;
    int d;
}  __attribute__ ((aligned (2))) test ;
#pragma pack(pop)

对应于 test类型的对齐值,大于 aligned 属性设置的最小对齐值的情况,这种情况下可以忽略aligned 属性。

✔结果就是20B 。
测试样例3:

typedef struct  subtest{
    char a;
    double b;
    char c;
    int d;
}subtest;

#pragma pack(push,2)
typedef struct  test{
    char a;
    subtest b;
}  __attribute__ ((aligned (4))) test ;
#pragma pack(pop)

① test 的内存对齐情况(先不考虑 aligned 属性)
typedef struct  subtest{
    char a;
    double b;
    char c;
    int d;
}subtest;

#pragma pack(push,2)
typedef struct  test{
    char a;    // 1B
    subtest b; //对于结构体subtest,原本以 8B做为对齐值
               //但这里设置了最大对齐值2,所以 填充1B,自身占据 24B
} test ;       // (1B) + (1B+24B) + 0B(已经是2的倍数,不需要再填充)
#pragma pack(pop)
② 最后来看 aligned 属性,设置的最小对齐值4 > 2,所以 test 结构体需要填充 2B,以构成4的倍数。

✔结果就是28B 。

七:含位域结构体的sizeof大小

前面已经说过,位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof,只是考虑到其特殊性而将其专门列了出来。C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。 使用位域的主要目的是压缩存储,

其大致规则为: 

1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字 段将紧邻前一个字段存储,直到不能容纳为止; 

2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字 段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 

3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方 式,Dev-C++采取压缩方式;

4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 

5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

 所以使用位域时不同编译器平台下占用大小不同,尽量还是自己实际测试一下。

测试:

struct test
{
    char a:1;
    char b:2;
    int c:3;
    char d:2;
};
test t1;

int len=sizeof(t1);   //len=12

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言字节对齐及__attribute__((aligned(n))) 与 #pragma(pack(n))的作用 的相关文章

随机推荐

  • 模拟电路设计(23)---模数和数模转换器概述

    ADC概念 现在我们对测温已经习以为常 进出公共场所 时不时就要测量体温 电子温度计对着你手腕 或额头 或耳朵 滴的一声 温度就显示出了 这个过程就涉及本文要介绍的模数转换 模数转换 即Analog to Digital Converter
  • 交换机VLAN及中继配置

    Vlan中继 trunk 是以太网接口之间的点对点链路 负责在单个链路上传输多个VLAN流量 1 配置vlan 一般都是使用这种全局静态配置模式 不使用数据库配置模式来配置vlan了 configure terminal config vl
  • java并发包:概论

    本文转载至 http blog csdn net a910626 article details 51900917 为什么要学习并发 今天和一哥们聊天 聊着聊着聊到钱的方面 当时我就说 全世界60亿人 要是每人给我一块钱那不就发财了啊 哥们
  • Android_API_28使用HTTP请求_笔记

    Google表示 为保证用户数据和设备的安全 针对下一代 Android 系统 Android P 的应用程序 将要求默认使用加密连接 这意味着 Android P 将禁止 App 使用所有未加密的连接 因此运行 Android P 系统的
  • Java中的方法(method)

    1 方法概述 什么是方法 方法是具有独立功能的代码块组织成为一个整体 使其具有特殊功能的代码集 注意 方法必须先创建才可以使用 该过程称为方法定义 方法创建后并不是直接运行的 需要手动使用后执行 该过程称为方法调用 2 方法的定义和调用 2
  • Vue 3 中动态获取高宽

    应用场景 一般用于父组件动态变换宽高 子组件需要同步修改宽高 实现简介 Vue3 写法 思路 1 监听父组件的 宽高 2 将监听到的高度赋给 你需要设置的对象 引入监听 并实现 如何得到动态宽度 此时的 div2 会与 divDom 宽度会
  • Spring Cloud Alibaba之服务容错组件 - Sentinel [规则持久化篇]

    规则持久化 拉模式 在Sentinel控制台对某个微服务的接口资源配置了流控 降级等规则后 若重启了该微服务 那么配置的相关规则就会丢失 因为Sentinel默认将规则存放在内存中 每次重启微服务都得重新配置规则显然是不合理的 所以我们需要
  • 量化投资学习-33:兼听则明,偏听则暗,多种指标综合适用-1

    兼听则明 偏听则暗 量化交易指标需要综合多个技术指标 一 上升期 1 上涨期上涨 买入与持有 1 支撑线 直线支撑 动态划线 均线支撑 稳定 历史数据 支撑线上 持有 2 波浪 1浪涨起点 3浪涨起点 5浪起点 3 均线 多头发散排列 5日
  • ❀数据集 ❀ 了解place365,运行出错解决。持续更新中...

    place365官网 Places A 10 million Image Database for Scene Recognition 官方说明 Places 数据集的设计遵循人类视觉认知的原则 我们的目标是建立一个视觉知识核心 可用于训练
  • WPF编程,Live Charts使用说明(40)——对称行(负堆积行)

    前台 using System using System Windows Controls using LiveCharts using LiveCharts Wpf namespace Wpf CartesianChart Negativ
  • 为啥一个java文件只能有一个public类

    以后 如果有人问你为什么一个java文件只能有一个public类呢 答 很简单啊 因为public类的名字要和java文件名相同 文件名只有一个 当然只能有一个public类 问 good 那为神马文件名必须要和public类名字相同呢 不
  • Blob+定位+特征来识别药片缺陷

    原图 定位 结果 代码 This example demonstrates an application from the pharmaceutical industry The task is to check the content o
  • 【Tensorflow2.0】基于Docker的Tensorflow2.x安装教程

    文章目录 1 Docker Engine安装 1 1 填加docker ce安装源到系统 1 2 docker ce 安装 1 3 国内安装环境设置 1 3 1 docker 国内源设置 1 3 2 docker hub国内源设置 1 3
  • remote: Invalid username or password. fatal: Authentication failed for .......

    最近一直没有使用GitHub提交代码 今天提交了点东西 发现怎么都push不成功 我就纳闷了 这段时间没有做任何事情 这是怎么回事呢 我使用的sourcetree提交代码 首先它会弹出一个框框让你输入 用户名密码 然而输入了很多次 还是re
  • 达梦数据库图形化管理界面manager打开报错

    达梦数据库管理工具manager打开报如下错误 Locking is not possible in the directory home diske dmdbms tool configuration org eclipse osgi A
  • Linux基础(2)用户操作

    该文章主要为完成实训任务及总结 详细实现过程及结果见 参考文章 参考文章 https howard2005 blog csdn net article details 126843544 学习目标 用户账号管理 Linux用户操作 Linu
  • 代码覆盖率和测试覆盖率_代码覆盖率与测试覆盖率; 哪个更好?

    代码覆盖率和测试覆盖率 测试覆盖率和代码覆盖率是衡量代码有效性的最流行方法 尽管这些术语有时会互换使用 因为它们的基本原理相同 但是它们并不像您想象的那样相似 很多时候 我注意到测试团队和开发团队对这两个术语的使用感到困惑 这就是为什么我想
  • VBA中实现数组排序的多种方法

    VBA里面没有现成的Sort方法可以使用 VBA里面要对数组进行排序 现有的通常做法 1 通过单元格赋值以后利用工作表里的Sort方法进行排序 2 通过SQL实现 也需要调用单元格区域存放数据 3 直接写循环语句通过算法来实现 除了上述方法
  • require js之define 函数

    require js define 函数 模块不同于传统的脚本文件 它良好地定义了一个作用域来避免全局名称空间污染 它可以显式地列出其依赖关系 并以函数 定义此模块的那个函数 参数的形式将这些依赖进行注入 而无需引用全局变量 Require
  • C语言字节对齐及__attribute__((aligned(n))) 与 #pragma(pack(n))的作用

    一 对齐规则 在没有 attribute aligned n 或 pragma pack修饰的声明下 字节对齐遵循下面三个原则 结构体 struct 的数据成员 第一个数据成员存放的地址为结构体变量偏移量为0的地址处 结构体变量的首地址能够