gcc__attribute__((aligned(alignment)))解析

原创 Linux二进制 2024-09-15 08:20

引言

在 C 语言中,内存对齐(Memory Alignment)是影响程序性能的重要因素之一。对齐不仅影响访问速度,还可能影响程序的正确性,特别是在硬件架构要求严格对齐的系统上。GNU C 编译器(GCC)提供了一种方便的机制来控制内存对齐:__attribute__((aligned(alignment)))。通过这个属性,我们可以控制变量、结构体或数组的对齐方式,以便优化性能或满足特殊需求。

本篇文章将深入介绍 __attribute__((aligned(alignment))) 的工作原理,并通过代码实例来演示它的应用场景。

工作原理

什么是内存对齐

内存对齐是指变量的地址满足特定的边界要求。通常,编译器会根据目标平台的要求自动分配对齐。例如,在 32 位系统上,int 类型的变量一般按 4 字节对齐,而 double 可能按 8 字节对齐。

通过对齐,可以保证 CPU 在读取或写入数据时,能够在最少的指令周期内完成访问。如果变量没有对齐到合适的边界,可能需要额外的指令进行内存操作,导致性能下降。

__attribute__((aligned(alignment)))的作用

__attribute__((aligned(alignment))) 是 GCC 提供的一个属性,用于手动设置变量或结构体的对齐方式。它允许你指定对齐边界,单位是字节。该属性可以在变量声明或类型定义时使用。它的作用包括:

  • 提高性能:通过为特定的变量设置合适的对齐方式,提升内存访问的效率。
  • 满足硬件要求:某些硬件或设备驱动程序可能要求特定的对齐方式,通过这个属性可以满足这些要求。
  • 处理 SIMD 指令:某些高级指令集(如 SSE、AVX)要求数据按 16 字节或 32 字节对齐。__attribute__((aligned)) 能帮助数据满足这些指令的对齐需求。

__attribute__((aligned(alignment))) 使用的基本语法如下:

type variable __attribute__((aligned(alignment)));

其中:

  • type 是变量类型。
  • variable 是变量名。
  • alignment 是对齐的字节数,通常是 2 的 n 次幂,n 为正整数,最小对齐单位为 2 字节。例如 2、4、8、16、32 等。

为 aligned 前后加上下划线,这允许您在头文件中使用这些属性,而不必关心可能同名的宏。例如,你可以使用 __aligned__ 代替 aligned

__attribute__ ((aligned(8))) 等同于 __attribute__ ((__aligned__(8)))

如果未指定 alignment,编译器会选择合适的对齐值,通常是变量类型的默认对齐方式。

代码演示

1. 修饰变量

源文件 test.c :

#include 
#include

struct NoAlign {
char a; // 1 字节
int b; // 4 字节
};

struct WithAlign {
char a; // 1 字节
int b __attribute__((aligned(8))); // 4 字节,强制 8 字节对齐
};

int main() {
// 打印结构体成员的偏移量以及整个结构体的大小
printf("NoAlign 结构体中:\n");
printf(" a 的偏移量: %zu\n", offsetof(struct NoAlign, a));
printf(" b 的偏移量: %zu\n", offsetof(struct NoAlign, b));
printf(" NoAlign 的大小: %zu\n", sizeof(struct NoAlign));

printf("\nWithAlign 结构体中:\n");
printf(" a 的偏移量: %zu\n", offsetof(struct WithAlign, a));
printf(" b 的偏移量: %zu\n", offsetof(struct WithAlign, b));
printf(" WithAlign 的大小: %zu\n", sizeof(struct WithAlign));

return 0;
}

上述代码解析如下:

结构体 NoAlign

  • char a 是 1 字节的字符类型。
  • int b 是 4 字节的整数类型,通常在大多数平台上按 4 字节对齐。

编译器为了让 int b 对齐到 4 字节,会在 char a 和 int b 之间插入 3 字节的填充。由于 b 是 4 字节对齐,所以整个结构体的大小也会被调整。

拓展:结构体对齐规则

1、第一个成员在与结构体变量偏移量为 0 的地址处。

2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 (对齐数结构体成员自身大小和默认对齐数的较小值VS 上默认对齐数是 8gcc 没有默认对齐数,对齐数就是变量本身的大小。

3、结构体总大小为最大对齐数(每个成员变量都有自己的对齐数)的整数倍。

4、针对嵌套结构体,嵌套的结构体要对齐到自己最大对齐数的整数倍处,结构体总大小是所有对齐数的最大值(包含嵌套结构体的对齐数)的整数倍。

结构体 WithAlign

  • char a 是 1 字节的字符类型。
  • int b 被强制要求使用 __attribute__((aligned(8))) 设置为 8 字节对齐。即使 int 默认是按 4 字节对齐,这里我们强制将其对齐到 8 字节边界。

编译器会在 char a 和 int b 之间插入 7 字节 的填充,以保证 b 的地址是 8 字节的倍数。并且,整个结构体的大小也必须是 8 的倍数。

编译运行上述程序,如下:

[root@localhost alianed]# gcc -o test test.c
[root@localhost alianed]# ls
test test.c
[root@localhost alianed]# ./test
NoAlign 结构体中:
a 的偏移量: 0
b 的偏移量: 4
NoAlign 的大小: 8

WithAlign 结构体中:
a 的偏移量: 0
b 的偏移量: 8
WithAlign 的大小: 16

NoAlign 结构体分析

  • a 的偏移量是 0,因为 a 位于结构体的起始位置。
  • b 的偏移量是 4,因为编译器在 a 后面插入了 3 个字节的填充,使 b 对齐到 4 字节边界。
  • 结构体的总大小是 8 字节,这是因为它的成员及其填充后总共占用了 8 字节的空间。

WithAlign 结构体分析

  • a 的偏移量是 0,同样位于结构体的起始位置。
  • b 的偏移量是 8,因为我们强制要求 b 按 8 字节对齐,因此在 a 后面插入了 7 字节的填充。
  • 结构体的总大小是 16 字节。由于 b 按 8 字节对齐,结构体的大小也必须是 8 字节的倍数,所以编译器在 b 后面补齐使结构体达到 16 字节。

通过上面的结果和分析,我们可以得出如下结论:

  • 未使用 __attribute__((aligned)) 修饰时,编译器会根据默认的类型对齐规则分配内存,并插入必要的填充。
  • 使用 __attribute__((aligned)) 修饰时,我们可以强制改变对齐方式,编译器会根据指定的对齐值插入填充字节,并调整结构体的大小。

通过查看输出中的偏移量和结构体大小,可以明显看出使用 __attribute__((aligned)) 后,内存布局发生了变化,尤其是当对齐要求大于成员类型的默认对齐时,填充字节的数量会显著增加。

2. 修饰结构体

源文件 test_struct.c :

#include 
#include

struct S2 {
short b[3]; // 一个包含 3 个 short 元素的数组
int a; // 一个 int 类型变量
char c; // 一个 char 类型变量
} __attribute__((aligned(8))); // 按照 8 字节对齐

int main() {
// 打印结构体成员的偏移量以及整个结构体的大小
printf("S2 结构体中:\n");
printf(" b 的偏移量: %zu\n", offsetof(struct S2, b));
printf(" a 的偏移量: %zu\n", offsetof(struct S2, a));
printf(" c 的偏移量: %zu\n", offsetof(struct S2, c));
printf(" S2 的大小: %zu\n", sizeof(struct S2));

return 0;
}

上述代码解析如下:

结构体 S2

  • short b[3]: 这是一个包含 3 个 short 类型元素的数组。short 的大小通常是 2 字节,因此 b 数组的大小为 3 * 2 = 6 字节。
  • int a: 这是一个 int 类型的变量,通常是 4 字节大小,默认按 4 字节对齐。
  • char cchar 只占用 1 字节,没有对齐要求。前面的 a 占用了 4 字节,因此 c 的偏移量紧接在 a 之后,即在 12 字节 处。
  • __attribute__((aligned(8))): 该属性强制 S2 结构体的对齐要求为 8 字节。即 S2 的起始地址和其所有成员的偏移量,必须是 8 的倍数。

拓展:offsetof

offsetof() 是一个宏,用来计算结构体中某个成员相对于结构体起始地址的偏移量。它的实现通过假设结构体的基地址为 0 来计算成员的地址偏移。原型如下:

#define offsetof(type, member) ((size_t) &(((type *)0)->member))

参数说明

  • type:结构体类型。
  • member:结构体中的成员名。

返回值

offsetof() 返回的是 member 成员相对于结构体起始地址的字节偏移量。返回的偏移量是一个无符号整数类型 size_t

代码解析

int main() {
// 打印结构体成员的偏移量以及整个结构体的大小
printf("S2 结构体中:\n");
printf(" b 的偏移量: %zu\n", offsetof(struct S2, b));
printf(" a 的偏移量: %zu\n", offsetof(struct S2, a));
printf(" c 的偏移量: %zu\n", offsetof(struct S2, c));
printf(" S2 的大小: %zu\n", sizeof(struct S2));

return 0;
}
  • offsetof(struct S2, b)

    • b 是结构体的第一个成员,数组 short b[3]。数组 b 的起始地址与结构体 S2 的起始地址相同,因此 b 的偏移量是 0 字节。
  • offsetof(struct S2, a)

    • a 是结构体的第二个成员,类型为 int。我们首先知道 b[3] 占用了 6 字节(3 * sizeof(short) = 6 字节)。

    • 默认情况下,int a 按 4 字节对齐,但由于 S2 结构体整体按 8 字节对齐,因此 a 必须按 8 字节对齐。为了满足 a 的 8 字节对齐要求,编译器会在 b[3] 之后插入 2 字节 的填充。所以 a 的偏移量是 8 字节,而不是 6 字节。

  • offsetof(struct S2, c)

    • char 只占用 1 字节,没有对齐要求。前面的 a 占用了 4 字节,因此 c 的偏移量紧接在 a 之后,即在 12 字节 处。因为结构体 S2 被要求按 8 字节对齐,最后还会插入填充字节来保证结构体的大小是 8 字节的倍数。
  • sizeof(struct S2)

    • 结构体的大小是成员大小和填充字节的总和,并且最终要满足 8 字节对齐的要求。b[3] 占用 6 字节。2 字节填充,使 a 对齐到 8 字节边界。a 占用 4 字节。c 占用 1 字节。为了满足结构体整体按 8 字节对齐的要求,编译器会在最后再插入 5 字节填充,使总大小为 16 字节。所以,sizeof(struct S2) 的结果是 16 字节

编译运行上述程序,如下:

[root@localhost alianed]# ./test_struct
S2 结构体中:
b 的偏移量: 0
a 的偏移量: 8
c 的偏移量: 12
S2 的大小: 16

__attribute__((aligned(8))) 修饰结构体时,主要作用是强制整个结构体按照 8 字节对齐,而不一定要求结构体内的每个成员都按照 8 字节对齐。具体而言:

  1. 结构体本身的对齐:结构体的起始地址会强制按照 8 字节对齐。这意味着当该结构体实例在内存中分配时,它的起始地址会是 8 的倍数。

  2. 结构体内部成员的对齐:每个成员依然根据其自身的数据类型以及默认的对齐要求进行对齐,但会考虑 __attribute__((aligned(8))) 对结构体整体对齐的影响。一般情况下,编译器会根据成员的类型和自身的对齐要求来安排它们的内存位置,插入必要的填充字节。

  3. 结构体大小的对齐:最终,结构体的大小会被调整为 8 的倍数。如果结构体内部的成员及其对齐填充导致结构体的大小不是 8 的倍数,编译器会在结构体的末尾插入填充字节,使整个结构体的大小满足 8 字节对齐的要求。

综合前面,可以得出结论:

  • 每个成员:按照其自身类型和默认对齐规则进行对齐,并不一定要按照 8 字节对齐。
  • 结构体整体:最终的结构体大小和起始地址都会按 8 字节对齐,编译器可能会在必要时在成员之间或末尾插入填充字节。

3. 修饰嵌套结构体

源文件 aligned.c :

#include
#include

void test()
{

struct S
{
int a;
char b[5];
int c;

};
printf("结构体S的大小为:%d \n", sizeof(struct S));
struct S1
{
int a;
char b;


};
printf("结构体S1的大小为:%d \n", sizeof(struct S1));

struct S2
{
int a;
char str[5];
struct S s __attribute((aligned(8))) ;

};
printf("\nS2 结构体中:\n");
printf("a 的偏移量: %zu\n", offsetof(struct S2, a));
printf("str 的偏移量: %zu\n", offsetof(struct S2, str));
printf("s 的偏移量: %zu\n", offsetof(struct S2, s));
printf("结构体S2的大小为:%d \n", sizeof(struct S2));

}

int main()
{
test();
return 0;
}

这段代码演示了几个结构体的内存布局和对齐,这里重点分析嵌套结构体的对齐方式,尤其是通过 __attribute__((aligned(8))) 设置的强制对齐。上述代码解析如下:

  • 结构体 Sint a 占用 4 字节,char b[5] 占用 5 字节,int c 占用 4 字节。结构体 S 的总大小为 16 字节(并不是 13 字节,因为编译器为 int c 对齐填充了 3 字节的空隙,使结构体大小对齐到 4 的倍数)。

  • 结构体 S1int a 占用 4 字节,char b 占用 1 字节。这里的总大小是 8 字节(而不是 5 字节),因为 char b 之后,编译器会插入 3 字节的填充,以确保结构体大小为最大对齐数 int 的倍数(即 4 字节的倍数),以提高对齐效率。

  • 结构体 S2int a 占用 4 字节,从偏移量 0 开始;char str[5] 紧接在 a 后面,占用 5 字节,偏移量为 4。由于 str 的结束地址是 9(从偏移量 4 开始), struct S 的 __attribute__((aligned(8))) 指定了 8 字节对齐,因此它的偏移量为 16,并且它的大小仍然为 16 字节。为了让 struct S 的地址对齐到 168 的倍数),编译器插入了 7 个字节的填充(确保 s 的偏移量为 16),故 struct S 的结束地址是 31 。由于结构体总大小是所有对齐数的最大值(包含嵌套结构体的对齐数)的整数倍,这里最大对齐数是 struct S 的 8,因此,结构体 S2 的总大小为 32 字节。

编译运行上面程序,结果如下:

[root@localhost alianed]# ./aligned
结构体S的大小为:16
结构体S1的大小为:8

S2 结构体中:
a 的偏移量: 0
str 的偏移量: 4
s 的偏移量: 16
结构体S2的大小为:32

__attribute__((aligned(8))) 修饰嵌套结构体时,会强制该结构体在内存中按照 8 字节对齐,而不是使用默认的对齐方式。它的作用主要体现在以下几个方面:

  1. 对齐要求提高:嵌套的结构体被强制按照 8 字节对齐,即使其中的成员变量默认只需 4 字节或更小的对齐,编译器也会在嵌套结构体前插入填充字节,以确保其起始地址为 8 的倍数。这是为了满足硬件对齐要求或者性能优化的需要。

  2. 影响内存布局:由于对齐要求的提高,编译器在嵌套结构体前可能会插入额外的填充字节,导致结构体的内存布局发生变化。这会影响嵌套结构体的起始偏移量,并可能增加整个结构体的大小。

  3. 性能优化:某些硬件平台(如 64 位系统或 SIMD 指令集)对数据对齐有较高要求,合理的对齐可以提升数据访问的效率。通过 __attribute__((aligned(8))),可以确保结构体在这些平台上更高效地被访问。

  4. 内存浪费的权衡:强制对齐可能导致一些内存的浪费,特别是在对齐边界和结构体成员大小不匹配时。但这种浪费通常是为了换取更好的访问性能,因此在需要对齐优化时应合理使用。

总结

__attribute__((aligned(alignment))) 是一种用于指定数据对齐方式的编译器属性,广泛应用于 C 和 C++ 编程中。通过对齐属性,开发者可以显式地控制数据在内存中的布局,从而优化程序性能,尤其是在涉及高性能计算、嵌入式系统和硬件接口等场景下。合理的对齐可以减少内存访问延迟,提高缓存效率,并避免处理器因数据未对齐而导致的异常处理开销。然而,不当使用对齐属性可能导致不必要的内存浪费或破坏数据布局的一致性,因此在应用时需谨慎考虑对齐需求与实际内存使用之间的平衡。总之,__attribute__((aligned(alignment))) 是一个强大的工具,能够帮助开发者在特定场景下获得更好的性能表现。


Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
评论
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 108浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 71浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 84浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 50浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 106浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 100浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦