Keil中三种手动结构体对齐方式,别用错了~

62bcb1de6ce928c5a5d91f6b3f91c9d5.gif

正文


大家好,我是bug菌~

最近移植了一些开源组件,发现较多的语法跟编译器相关,如果没有跨平台处理,确实大大降低了程序的可移植性,其中尤为突出的就是结构体字节对齐属性的标识,通常编译器采用默认字节对齐方式,按照处理器架构的要求来决定的。

比如如下结构体在stm32中默认为4字节对齐:

typedef struct _tag_Test1
  {
     uint8_t member1;
     uint16_t member2;
  }sTest1;
 
  stSize = sizeof(sTest1);

自然sizeof获得的结构体大小也是4。

然而默认对齐方式有时候并不满足我们编程的需求,比如需要降低一些内存占用,或者提高相关数据的访问效率等等,我们会手动的声明相关变量的对齐方式。

那么这里总结了下AC5编译器进行字节对齐的几种方式:

1

#pragma pack

#pragma pack 是一个编译指令,用于指定结构体、联合体和类成员的字节对齐方式。在 Keil uVision5 中,可以使用 #pragma pack 指令来设置字节对齐方式。一般我们用如下方式标识#pragma pack(n)

其中,n 是对齐系数,表示按照 n 字节对齐。常见的对齐系数包括 1、2、4、8 等。例如,若要将对齐系数设置为 4,比如:#pragma pack(4),该指令通常放置在结构体、联合体或类的定义之前,以影响其后的所有定义,这里尤其需要注意,很多时候忘记恢复字节对齐导致了一些没必要的问题。这样一来,所有在 #pragma pack 后声明的结构体、联合体或类成员都将按照指定的字节对齐方式进行排列。

那么如果我们需要取消则需要采用#pragma pack () 来取消结构体对齐。

#pragma pack (1)
  typedef struct _tag_Test2
  {
      uint8_t member1;
      uint16_t member2;
  
  }sTest2;
  #pragma pack () // 取消结构体对齐
  stSize = sizeof(sTest2);

通过这样的定义,使得sTest2结构体整体大小只占用3个字节在,这种方式在MDK中比较常用。

当然有经验的朋友该说了,我用#pragma pack(push,n)比较多,没错,该语法也同样是可以的,比如例子:

//#pragma pack (1)
    #pragma pack(push,1) 
    typedef struct _tag_Test2
    {
        uint8_t member1;
        uint16_t member2;

    }sTest2;
    //#pragma pack () // 取消结构体对齐
    #pragma pack(pop)

    stSize = sizeof(sTest2);

既然都聊到这个份上了,该谈谈他们的差异了:

#pragma pack(n) 和 #pragma pack(push, n) 其实在功能上没太大的区别,仅仅只是在使用方面略有不同。

#pragma pack(n):

  • 这个指令直接设置当前字节对齐系数为 n。这意味着在此指令之后声明的结构体、联合体或类成员都将按照指定的字节对齐方式进行排列。

  • 每次使用 #pragma pack(n) 时,都会覆盖之前的对齐设置,因此它可能会影响后续的代码。

  • 没有保存当前的对齐方式,因此在使用完之后,如果需要还原到先前的对齐方式,就需要手动重新设置。

#pragma pack(push, n):

  • 这个指令其实也是设置当前字节对齐系数为 n。但是它还有一个功能,就是将当前的对齐方式保存到编译器的栈中。

  • 这意味着,使用 #pragma pack(push, n) 后,可以在代码的后续部分使用 #pragma pack(pop) 来恢复之前保存的对齐方式,而不会受到之后代码中 #pragma pack 指令的影响。

  • 因此,#pragma pack(push, n) 更灵活,可以避免在代码的后续部分不小心修改了对齐方式而导致错误。

2

__attribute__((__packed__))

__attribute__((__packed__)) 是 GCC 和一些兼容 GCC 的编译器(如 Clang)提供的一个特性,用于指示编译器以紧凑的方式存储结构体或类,即取消对齐。

使用案例如下:

typedef  struct __attribute__ ((__packed__)) _tag_Test3 
    {
        uint8_t member1;
        uint16_t member2;

    }sTest3; 

    stSize = sizeof(sTest3);

那么最终结构体的大小也将是3个字节。

3

干脆的__packed

相信有经验的各位都比较喜欢使用这个属性吧:

typedef __packed struct _tag_Test4 
    {
        uint8_t member1;
        uint16_t member2;

    }sTest4;    ;

    stSize = sizeof(sTest4);

__packed 是一种特性,指示编译器取消对其成员的自然对齐。它的作用类似于 __attribute__((__packed__)),但是它更加与平台无关,因为它是一种更通用的约定,不依赖于特定编译器。

虽然说__packed 是一种常见的约定,但它并非标准 C 语言的一部分,因此在不同的编译器和平台上可能具有不同的行为,使用时应谨慎考虑平台兼容性和性能问题。

最后

      好了,今天就跟大家分享这么多了,如果你觉得有所收获,一定记得点个~

bug菌唯一、永久、免费分享嵌入式技术知识平台~

推荐专辑  点击蓝色字体即可跳转

☞  MCU进阶专辑 9908f4764f2473a781dfe94d123b288f.gif

☞  嵌入式C语言进阶专辑 f2daef2f3019a5e69592b06044ed3343.gif

☞  “bug说”专辑 bc6c242e888aaedd6e75dbd96f75261e.gif

☞ 专辑|Linux应用程序编程大全

☞ 专辑|学点网络知识

☞ 专辑|手撕C语言

☞ 专辑|手撕C++语言

☞ 专辑|经验分享

☞ 专辑|电能控制技术

☞ 专辑 | 从单片机到Linux

d556353038739a503d88eb9a74708434.gif

Keil uVision等嵌入式开发环境中,结构体(Structures)是一种用户自定义的数据类型,用于组合不同类型的数据成员,以便更好地组织和管理数据。以下是结构体定义、使用的基本步骤和一些原则: **定义结构体:** ```c typedef struct { uint8_t byte1; // 字节类型的数据成员 int16_t word2; // 整型数据成员 float real3; // 浮点数数据成员 } MyStruct; ``` 在这个例子中,`MyStruct`是一个结构体名称,其中包含三个数据成员。 **定义和初始化结构体变量:** ```c MyStruct myData = {0x42, 100, 3.14f}; // 初始化各个成员 ``` **使用结构体:** 1. 可以通过`.`运算符访问结构体成员: ```c printf("Byte1: %d\n", myData.byte1); printf("Word2: %d\n", myData.word2); printf("Real3: %.2f\n", myData.real3); ``` 2. 将结构体作为函数参数传递,或返回值: ```c void printStruct(MyStruct data) { printf("%s\n", "Struct data: "); printDetails(data); // 自定义函数,打印结构体细节 } int main(void) { printStruct(myData); return 0; } ``` **原则与注意事项:** 1. 结构体设计应尽可能清晰明了,避免过多复杂的成员,易于理解和维护。 2. 数据成员可以是有类型的,也可以是无类型的(如void*),但需要谨慎处理。 3. 结构体默认是不可变的(const struct),如果需要修改成员,需确保提供修改成员的合理方法。 4. 如果结构体成员很多,考虑是否应该拆分成多个更小的结构体。 5. 使用预处理器宏(#define)创建常量结构体,可能会降低代码的可读性和灵活性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最后一个bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值