0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

使用#pragma pack(n)的注意事项与问题案例分享

嵌入式USB开发 来源:嵌入式USB开发 作者:嵌入式USB开发 2023-09-19 14:02 次阅读

本文转自公众号,欢迎关注
https://mp.weixin.qq.com/s/uzaGLFTDBAn8wyR84yaiIw

0.背景

本文记录很久之前在一个项目中遇到的”幽灵问题”,结构体读写异常,虽然最终结论很简单,遇到过类似问题或者了解对应知识点的可能一眼就知道了,但是没遇到过的可能会花费很多时间去定位甚至无从下手。这就是经验的重要性,所以特分享出这篇文章。结论本身没有很大的技术含量,但是中间涉及的思想,态度,解决问题的思路,过程,如何形成标准,避免类似问题等等确是我们嵌入式开发中的共性问题。

1.问题回顾

1.1历史问题1 在不同地方,结构体访问按照不同对齐方式访问,开始怀疑keil编译器的问题。之前还换了keil的不同版本去试都是一样。

之前can驱动在改了某版本代码后突然收不到数据,调试记录如下:写和读时结构体对齐方式不一样。

mdk未显式指定结构体对齐方式时,通过.访问成员变量,可能不同地方对齐方式不一样。

Mdk版本v5.xx ARM CC编译器V5.06

写结构体成员CAN1->sFIFOMailBox[1].RIR 查看对应的汇编代码是STR r0 [SP,#0x0C]

即RIR成员变量偏移地址是0xC,此时采用自然对齐非压缩方式。写进去的值是0x00 22 E2 F0。

图片

读结构体成员CAN1->sFIFOMailBox[1].RIR 查看对应的汇编代码是LDR r0 [SP,#0x0A]

即RIR成员变量偏移地址是0xA。与写时偏移地址不一样,此时采用了压缩方式, 读出来的值是E2 EF A5 A5,偏移了2字节。查看内存,实际内存的值是对的,只是结构体访问时对应汇编代码成员变量的的偏移地址不对,导致解析错误,如果按写入时的偏移0x0C解析读到的值是0x0022E2EF就是正确的。

图片

解决办法:暂时不确定是编译器问题还是配置问题,手动显式设置结构体对齐方式,可解决该问题。

1.2历史问题2 某些结构体增加#pragma pack(1)导致后导致系统异常。

当时修改代码后未复现,没有记录现场。

2问题分析过程

查找问题起始点

前面花了差不多一天时间去对比代码逐渐删除,最终定位到driver_can.h增加以下代码

就有问题不加就没问题

#pragma pack(1)


typedef struct


{


uint16_t rx_in_u16;              /**< 接收缓冲区写入指针       */


uint16_t rx_out_u16;             /**< 接收缓冲区读出指针       */


uint16_t rx_len_u16;             /**< 接收缓冲区有效数据大小   */


uint16_t tx_in_u16;              /**< 发送缓冲区写入指针       */


uint16_t tx_out_u16;             /**< 发送缓冲区读出指针       */


uint16_t tx_len_u16;             /**< 发送缓冲区有效数据大小   */


uint16_t rxbuf_len_u16;          /**< 接收缓冲区大小           */   


uint16_t txbuf_len_u16;          /**< 发送缓冲区大小           */


driver_can_data_t *rx_buf_pt;    /**< 接收缓冲区               */


driver_can_data_t *tx_buf_pt;    /**< 发送缓冲区               */


}driver_can_t;

进一步验证

找到出现问题的代码后就一步步跟踪

在头文件中driver_can.h中定义了

图片

在osapi.c中 include “driver_can.h”

导致以下代码 绿色语句执行后出错。

图片

在osapi.c中 不包含 driver_can.h

图片

上述现象无

调试分析

仿真器跟踪调试对比有问题和无问题的代码执行时的环境(变量地址 变量值等)

先包含driver.h 有问题时情况如下:

Osapi.c中如下代码执行

图片

图片

可以看出进入函数uxTaskGetSystemState执行前pxTaskStatusArray的eCurrentState和uxCurrentPriority只相差1,说明是pack(1)模式

进入函数uxTaskGetSystemStat后(在task.c中) 看到红色部分变了,pxTaskStatusArray的eCurrentState和uxCurrentPriority相差4,说明是非pack(1)模式

图片

图片

在后面继续给uxCurrentPriority等成员赋值时实际上溢出了,因为传入pxTaskStatusArray的是复函数malloc出来的,所以这里溢出将导致malloc的链表关系破坏导致整个堆环境破坏,后面问题会蔓延最终导致灾难性错误。

如果上面的pxTaskStatusArray不是malloc出来的而是栈中的临时变量则会导致栈破坏,最终问题也可能蔓延导灾难性的错误。

而不包含driver.h时

进入函数uxTaskGetSystemState前

图片

图片

进入函数后 没有变

图片

图片

最终原因

从上可以看出,因为pxTaskStatusArray对应结构体是没有TaskStatus_t显示指定对齐模式的,

Osapi包含了driver.h的#pragma pack(1)所以osapi整个文件中没有显示指定的对齐模式的结构体都按照pack(1)对齐,而task.c中按照默认对齐方式(4字节),所以导致错误。

实际上这里是#pragma pack(1)的用法错误 正确用法见《总结》

上述分析过程在keil中也是一样的,所以之前怀疑的keil编译有问题是错误的,跟编译器没有关系,是pack(1)指令使用错误导致。

3.问题回顾

回顾问题一

为什么不同地方结构体访问不同?

是因为当时有些地方的头文件中增加了#pragma pack(1),有些c文件包含了该头文件,有写c文件没有包含该文件。在包含了该头文件的c文件中所有没有显示指定对齐模式的结构将会按照pack(1)模式,没有包含该头文件的c文件中则会按照编译器默认的对齐模式。所以导致不同c文件对齐模式不一样,关键是看有包含的头文件中有#pragma pack(1)

为什么对结构体显示的指定对齐模式后就没问题?

#pragma pack(1)的含义是: c文件#pragma pack(1)指令后所有没有显示指定对齐模式的结构体都会按照pack(1)对齐。

对于显示指定对齐模式的结构体按照指定对齐模式,所以显示指定后不受#pragma pack(1)影响

回顾问题二

为什么不知何故加了些代码就好了?

因为有问题的c代码中没有包含有#pragma pack(1)的头文件,或者结构体显示的增加了对齐模式。

总结

结构体对齐方式的指定有两种,推荐使用第一种

l第一种: 直接对结构体显式定义对齐模式 这种方式一般使用于头文件申明时

对于支持gcc属性扩展的编译器(IAR KEIL新版本都支持) 使用

例如

typedef struct __attribute__ ((__packed__)) loop_to_channel


{


uint8_t loop;


gpio_ch_e ch;


} loop_to_channel_t;


对于IAR还可以使用__packed


/**


* struct driver_can_status_t


* CAN状态结构体.


*/


typedef __packed struct


{


uint8_t send_err;              /**< 发送错误帧计数     */


uint8_t rcv_err;               /**< 接收错误帧计数     */


uint32_t send_frames;          /**< 发送帧数           */


uint32_t rcv_frames;           /**< 接受帧数           */


uint32_t esr;                  /**< 状态寄存器         */


}driver_can_status_t;

l第二种: #pragma pack(1) 这种方式一般使用于c文件中对本文件设置后所有地方生效

这种方式一定要注意恢复设置

正确示例

#pragma pack(push)


#pragma pack(1)


typedef struct


{


uint16_t rx_in_u16;              /**< 接收缓冲区写入指针       */


uint16_t rx_out_u16;             /**< 接收缓冲区读出指针       */


uint16_t rx_len_u16;             /**< 接收缓冲区有效数据大小   */


uint16_t tx_in_u16;              /**< 发送缓冲区写入指针       */


uint16_t tx_out_u16;             /**< 发送缓冲区读出指针       */


uint16_t tx_len_u16;             /**< 发送缓冲区有效数据大小   */


uint16_t rxbuf_len_u16;          /**< 接收缓冲区大小           */   


uint16_t txbuf_len_u16;          /**< 发送缓冲区大小           */


driver_can_data_t *rx_buf_pt;    /**< 接收缓冲区               */


driver_can_data_t *tx_buf_pt;    /**< 发送缓冲区               */


}driver_can_t;


#pragma pack(pop)

错误示例

#pragma pack(1)


typedef struct


{


uint16_t rx_in_u16;              /**< 接收缓冲区写入指针       */


uint16_t rx_out_u16;             /**< 接收缓冲区读出指针       */


uint16_t rx_len_u16;             /**< 接收缓冲区有效数据大小   */


uint16_t tx_in_u16;              /**< 发送缓冲区写入指针       */


uint16_t tx_out_u16;             /**< 发送缓冲区读出指针       */


uint16_t tx_len_u16;             /**< 发送缓冲区有效数据大小   */


uint16_t rxbuf_len_u16;          /**< 接收缓冲区大小           */   


uint16_t txbuf_len_u16;          /**< 发送缓冲区大小           */


driver_can_data_t *rx_buf_pt;    /**< 接收缓冲区               */


driver_can_data_t *tx_buf_pt;    /**< 发送缓冲区               */


}driver_can_t;

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4984

    文章

    18300

    浏览量

    288971
  • keil
    +关注

    关注

    68

    文章

    1196

    浏览量

    165430
  • 编译器
    +关注

    关注

    1

    文章

    1577

    浏览量

    48647
  • 结构体
    +关注

    关注

    1

    文章

    125

    浏览量

    10754
收藏 人收藏

    评论

    相关推荐

    胆机使用的注意事项

    胆机使用的注意事项:胆机使用的注意事项 我是初哥, 现在对胆机感兴趣, 但听说胆机使用麻烦, 请问有什么需要注意的?湖南吉首火车站 范增不必担心, 胆机的使用方法
    发表于 11-29 17:09 46次下载

    LCM使用注意事项

    LCM使用注意事项 1. 安装   LCD模块的安装是用PCB上的安
    发表于 04-16 21:38 1336次阅读

    DS2746应用注意事项

    DS2746应用注意事项 Abstract: The DS2746 has two auxiliary inputs to allow voltage sampling of resistor
    发表于 04-30 13:54 756次阅读
    DS2746应用<b class='flag-5'>注意事项</b>

    聚合物电池PACK操作注意事项

    聚合物电池PACK操作注意事项 一. 电芯操作注意事项 1. 外包装铝塑复合膜: 聚合物电池的外包装是铝塑复合膜,很容易被尖锐
    发表于 10-24 16:49 3037次阅读

    硒鼓注意事项

    硒鼓注意事项     1、避免在高湿、高温、高寒环
    发表于 12-28 15:47 1133次阅读

    傲龙微晶屏的使用技巧和注意事项

    傲龙微晶屏的使用技巧和注意事项
    发表于 02-10 10:03 586次阅读

    钽电解应用注意事项

    钽电解应用注意事项 使用电压---------------------------------------------------------------------------------------------------------------------电容器
    发表于 03-31 15:54 558次阅读

    浅谈PCB板设计注意事项

    在设计PCB板时应注意的一些基本事项:相关PCB设计参数详解以及相关注意事项
    发表于 05-09 16:05 3369次阅读

    航拍技巧操作及注意事项交流分享

    航拍技巧操作及注意事项交流分享
    发表于 01-15 16:03 0次下载

    EMI走线注意事项合集

    EMI走线注意事项合集
    发表于 12-20 15:57 67次下载

    使用pragma pack函数修改对齐方式应用笔记

    AN1207 使用pragma pack函数修改对齐方式应用笔记,本文介绍了如何使使用#pragma pack ()函数修改对齐
    发表于 05-10 14:46 0次下载
    使用<b class='flag-5'>pragma</b> <b class='flag-5'>pack</b>函数修改对齐方式应用笔记

    使用注意事项

    使用注意事项
    发表于 03-17 20:14 0次下载
    使用<b class='flag-5'>注意事项</b>

    使用注意事项

    使用注意事项
    发表于 07-07 19:04 0次下载
    使用<b class='flag-5'>注意事项</b>

    中8位MCU EEPROM使用注意事项

    中颖8位MCU EEPROM使用注意事项
    的头像 发表于 09-27 15:34 511次阅读
    中8位MCU EEPROM使用<b class='flag-5'>注意事项</b>

    展频IC布板注意事项

    展频IC布板注意事项
    发表于 04-14 10:12 4次下载