<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
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;

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 嵌入式
    +關注

    關注

    5001

    文章

    18406

    瀏覽量

    291328
  • keil
    +關注

    關注

    68

    文章

    1196

    瀏覽量

    165611
  • 編譯器
    +關注

    關注

    1

    文章

    1583

    瀏覽量

    48729
  • 結構體
    +關注

    關注

    1

    文章

    126

    瀏覽量

    10760
收藏 人收藏

    評論

    相關推薦

    膽機使用的注意事項

    膽機使用的注意事項:膽機使用的注意事項 我是初哥, 現在對膽機感興趣, 但聽說膽機使用麻煩, 請問有什么需要注意的?湖南吉首火車站 范增不必擔心, 膽機的使用方法
    發表于 11-29 17:09 ?46次下載

    LCM使用注意事項

    LCM使用注意事項 1. 安裝   LCD模塊的安裝是用PCB上的安
    發表于 04-16 21:38 ?1353次閱讀

    DS2746應用注意事項

    DS2746應用注意事項 Abstract: The DS2746 has two auxiliary inputs to allow voltage sampling of resistor
    發表于 04-30 13:54 ?762次閱讀
    DS2746應用<b class='flag-5'>注意事項</b>

    聚合物電池PACK操作注意事項

    聚合物電池PACK操作注意事項 一. 電芯操作注意事項 1. 外包裝鋁塑復合膜: 聚合物電池的外包裝是鋁塑復合膜,很容易被尖銳
    發表于 10-24 16:49 ?3078次閱讀

    電池組的設計加工注意事項

    電池組的設計加工注意事項 電池組設計注意事項: 1、功率要求 2、體積空
    發表于 11-05 08:47 ?1404次閱讀

    硒鼓注意事項

    硒鼓注意事項     1、避免在高濕、高溫、高寒環
    發表于 12-28 15:47 ?1146次閱讀

    傲龍微晶屏的使用技巧和注意事項

    傲龍微晶屏的使用技巧和注意事項
    發表于 02-10 10:03 ?605次閱讀

    鉭電解應用注意事項

    鉭電解應用注意事項 使用電壓---------------------------------------------------------------------------------------------------------------------電容器
    發表于 03-31 15:54 ?560次閱讀

    淺談PCB板設計注意事項

    在設計PCB板時應注意的一些基本事項:相關PCB設計參數詳解以及相關注意事項
    發表于 05-09 16:05 ?3380次閱讀

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

    航拍技巧操作及注意事項交流分享
    發表于 01-15 16:03 ?0次下載

    使用pragma pack函數修改對齊方式應用筆記

    AN1207 使用pragma pack函數修改對齊方式應用筆記,本文介紹了如何使使用#pragma pack ()函數修改對齊
    發表于 05-10 14:46 ?1次下載
    使用<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 ?593次閱讀
    中8位MCU EEPROM使用<b class='flag-5'>注意事項</b>

    展頻IC布板注意事項

    展頻IC布板注意事項
    發表于 04-14 10:12 ?4次下載
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>