0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

使用#pragma pack(n)的注意事項(xiàng)與問題案例分享

嵌入式USB開發(fā) ? 來源:嵌入式USB開發(fā) ? 作者:嵌入式USB開發(fā) ? 2023-09-19 14:02 ? 次閱讀

本文轉(zhuǎn)自公眾號(hào),歡迎關(guān)注
https://mp.weixin.qq.com/s/uzaGLFTDBAn8wyR84yaiIw

0.背景

本文記錄很久之前在一個(gè)項(xiàng)目中遇到的”幽靈問題”,結(jié)構(gòu)體讀寫異常,雖然最終結(jié)論很簡(jiǎn)單,遇到過類似問題或者了解對(duì)應(yīng)知識(shí)點(diǎn)的可能一眼就知道了,但是沒遇到過的可能會(huì)花費(fèi)很多時(shí)間去定位甚至無從下手。這就是經(jīng)驗(yàn)的重要性,所以特分享出這篇文章。結(jié)論本身沒有很大的技術(shù)含量,但是中間涉及的思想,態(tài)度,解決問題的思路,過程,如何形成標(biāo)準(zhǔn),避免類似問題等等確是我們嵌入式開發(fā)中的共性問題。

1.問題回顧

1.1歷史問題1 在不同地方,結(jié)構(gòu)體訪問按照不同對(duì)齊方式訪問,開始懷疑keil編譯器的問題。之前還換了keil的不同版本去試都是一樣。

之前can驅(qū)動(dòng)在改了某版本代碼后突然收不到數(shù)據(jù),調(diào)試記錄如下:寫和讀時(shí)結(jié)構(gòu)體對(duì)齊方式不一樣。

mdk未顯式指定結(jié)構(gòu)體對(duì)齊方式時(shí),通過.訪問成員變量,可能不同地方對(duì)齊方式不一樣。

Mdk版本v5.xx ARM CC編譯器V5.06

寫結(jié)構(gòu)體成員CAN1->sFIFOMailBox[1].RIR 查看對(duì)應(yīng)的匯編代碼是STR r0 [SP,#0x0C]

即RIR成員變量偏移地址是0xC,此時(shí)采用自然對(duì)齊非壓縮方式。寫進(jìn)去的值是0x00 22 E2 F0。

圖片

讀結(jié)構(gòu)體成員CAN1->sFIFOMailBox[1].RIR 查看對(duì)應(yīng)的匯編代碼是LDR r0 [SP,#0x0A]

即RIR成員變量偏移地址是0xA。與寫時(shí)偏移地址不一樣,此時(shí)采用了壓縮方式, 讀出來的值是E2 EF A5 A5,偏移了2字節(jié)。查看內(nèi)存,實(shí)際內(nèi)存的值是對(duì)的,只是結(jié)構(gòu)體訪問時(shí)對(duì)應(yīng)匯編代碼成員變量的的偏移地址不對(duì),導(dǎo)致解析錯(cuò)誤,如果按寫入時(shí)的偏移0x0C解析讀到的值是0x0022E2EF就是正確的。

圖片

解決辦法:暫時(shí)不確定是編譯器問題還是配置問題,手動(dòng)顯式設(shè)置結(jié)構(gòu)體對(duì)齊方式,可解決該問題。

1.2歷史問題2 某些結(jié)構(gòu)體增加#pragma pack(1)導(dǎo)致后導(dǎo)致系統(tǒng)異常。

當(dāng)時(shí)修改代碼后未復(fù)現(xiàn),沒有記錄現(xiàn)場(chǎng)。

2問題分析過程

查找問題起始點(diǎn)

前面花了差不多一天時(shí)間去對(duì)比代碼逐漸刪除,最終定位到driver_can.h增加以下代碼

就有問題不加就沒問題

#pragma pack(1)


typedef struct


{


uint16_t rx_in_u16;              /**< 接收緩沖區(qū)寫入指針       */


uint16_t rx_out_u16;             /**< 接收緩沖區(qū)讀出指針       */


uint16_t rx_len_u16;             /**< 接收緩沖區(qū)有效數(shù)據(jù)大小   */


uint16_t tx_in_u16;              /**< 發(fā)送緩沖區(qū)寫入指針       */


uint16_t tx_out_u16;             /**< 發(fā)送緩沖區(qū)讀出指針       */


uint16_t tx_len_u16;             /**< 發(fā)送緩沖區(qū)有效數(shù)據(jù)大小   */


uint16_t rxbuf_len_u16;          /**< 接收緩沖區(qū)大小           */   


uint16_t txbuf_len_u16;          /**< 發(fā)送緩沖區(qū)大小           */


driver_can_data_t *rx_buf_pt;    /**< 接收緩沖區(qū)               */


driver_can_data_t *tx_buf_pt;    /**< 發(fā)送緩沖區(qū)               */


}driver_can_t;

進(jìn)一步驗(yàn)證

找到出現(xiàn)問題的代碼后就一步步跟蹤

在頭文件中driver_can.h中定義了

圖片

在osapi.c中 include “driver_can.h”

導(dǎo)致以下代碼 綠色語(yǔ)句執(zhí)行后出錯(cuò)。

圖片

在osapi.c中 不包含 driver_can.h

圖片

上述現(xiàn)象無

調(diào)試分析

仿真器跟蹤調(diào)試對(duì)比有問題和無問題的代碼執(zhí)行時(shí)的環(huán)境(變量地址 變量值等)

先包含driver.h 有問題時(shí)情況如下:

Osapi.c中如下代碼執(zhí)行

圖片

圖片

可以看出進(jìn)入函數(shù)uxTaskGetSystemState執(zhí)行前pxTaskStatusArray的eCurrentState和uxCurrentPriority只相差1,說明是pack(1)模式

進(jìn)入函數(shù)uxTaskGetSystemStat后(在task.c中) 看到紅色部分變了,pxTaskStatusArray的eCurrentState和uxCurrentPriority相差4,說明是非pack(1)模式

圖片

圖片

在后面繼續(xù)給uxCurrentPriority等成員賦值時(shí)實(shí)際上溢出了,因?yàn)閭魅雙xTaskStatusArray的是復(fù)函數(shù)malloc出來的,所以這里溢出將導(dǎo)致malloc的鏈表關(guān)系破壞導(dǎo)致整個(gè)堆環(huán)境破壞,后面問題會(huì)蔓延最終導(dǎo)致災(zāi)難性錯(cuò)誤。

如果上面的pxTaskStatusArray不是malloc出來的而是棧中的臨時(shí)變量則會(huì)導(dǎo)致棧破壞,最終問題也可能蔓延導(dǎo)災(zāi)難性的錯(cuò)誤。

而不包含driver.h時(shí)

進(jìn)入函數(shù)uxTaskGetSystemState前

圖片

圖片

進(jìn)入函數(shù)后 沒有變

圖片

圖片

最終原因

從上可以看出,因?yàn)閜xTaskStatusArray對(duì)應(yīng)結(jié)構(gòu)體是沒有TaskStatus_t顯示指定對(duì)齊模式的,

Osapi包含了driver.h的#pragma pack(1)所以osapi整個(gè)文件中沒有顯示指定的對(duì)齊模式的結(jié)構(gòu)體都按照pack(1)對(duì)齊,而task.c中按照默認(rèn)對(duì)齊方式(4字節(jié)),所以導(dǎo)致錯(cuò)誤。

實(shí)際上這里是#pragma pack(1)的用法錯(cuò)誤 正確用法見《總結(jié)》

上述分析過程在keil中也是一樣的,所以之前懷疑的keil編譯有問題是錯(cuò)誤的,跟編譯器沒有關(guān)系,是pack(1)指令使用錯(cuò)誤導(dǎo)致。

3.問題回顧

回顧問題一

為什么不同地方結(jié)構(gòu)體訪問不同?

是因?yàn)楫?dāng)時(shí)有些地方的頭文件中增加了#pragma pack(1),有些c文件包含了該頭文件,有寫c文件沒有包含該文件。在包含了該頭文件的c文件中所有沒有顯示指定對(duì)齊模式的結(jié)構(gòu)將會(huì)按照pack(1)模式,沒有包含該頭文件的c文件中則會(huì)按照編譯器默認(rèn)的對(duì)齊模式。所以導(dǎo)致不同c文件對(duì)齊模式不一樣,關(guān)鍵是看有包含的頭文件中有#pragma pack(1)

為什么對(duì)結(jié)構(gòu)體顯示的指定對(duì)齊模式后就沒問題?

#pragma pack(1)的含義是: c文件#pragma pack(1)指令后所有沒有顯示指定對(duì)齊模式的結(jié)構(gòu)體都會(huì)按照pack(1)對(duì)齊。

對(duì)于顯示指定對(duì)齊模式的結(jié)構(gòu)體按照指定對(duì)齊模式,所以顯示指定后不受#pragma pack(1)影響

回顧問題二

為什么不知何故加了些代碼就好了?

因?yàn)橛袉栴}的c代碼中沒有包含有#pragma pack(1)的頭文件,或者結(jié)構(gòu)體顯示的增加了對(duì)齊模式。

總結(jié)

結(jié)構(gòu)體對(duì)齊方式的指定有兩種,推薦使用第一種

l第一種: 直接對(duì)結(jié)構(gòu)體顯式定義對(duì)齊模式 這種方式一般使用于頭文件申明時(shí)

對(duì)于支持gcc屬性擴(kuò)展的編譯器(IAR KEIL新版本都支持) 使用

例如

typedef struct __attribute__ ((__packed__)) loop_to_channel


{


uint8_t loop;


gpio_ch_e ch;


} loop_to_channel_t;


對(duì)于IAR還可以使用__packed


/**


* struct driver_can_status_t


* CAN狀態(tài)結(jié)構(gòu)體.


*/


typedef __packed struct


{


uint8_t send_err;              /**< 發(fā)送錯(cuò)誤幀計(jì)數(shù)     */


uint8_t rcv_err;               /**< 接收錯(cuò)誤幀計(jì)數(shù)     */


uint32_t send_frames;          /**< 發(fā)送幀數(shù)           */


uint32_t rcv_frames;           /**< 接受幀數(shù)           */


uint32_t esr;                  /**< 狀態(tài)寄存器         */


}driver_can_status_t;

l第二種: #pragma pack(1) 這種方式一般使用于c文件中對(duì)本文件設(shè)置后所有地方生效

這種方式一定要注意恢復(fù)設(shè)置

正確示例

#pragma pack(push)


#pragma pack(1)


typedef struct


{


uint16_t rx_in_u16;              /**< 接收緩沖區(qū)寫入指針       */


uint16_t rx_out_u16;             /**< 接收緩沖區(qū)讀出指針       */


uint16_t rx_len_u16;             /**< 接收緩沖區(qū)有效數(shù)據(jù)大小   */


uint16_t tx_in_u16;              /**< 發(fā)送緩沖區(qū)寫入指針       */


uint16_t tx_out_u16;             /**< 發(fā)送緩沖區(qū)讀出指針       */


uint16_t tx_len_u16;             /**< 發(fā)送緩沖區(qū)有效數(shù)據(jù)大小   */


uint16_t rxbuf_len_u16;          /**< 接收緩沖區(qū)大小           */   


uint16_t txbuf_len_u16;          /**< 發(fā)送緩沖區(qū)大小           */


driver_can_data_t *rx_buf_pt;    /**< 接收緩沖區(qū)               */


driver_can_data_t *tx_buf_pt;    /**< 發(fā)送緩沖區(qū)               */


}driver_can_t;


#pragma pack(pop)

錯(cuò)誤示例

#pragma pack(1)


typedef struct


{


uint16_t rx_in_u16;              /**< 接收緩沖區(qū)寫入指針       */


uint16_t rx_out_u16;             /**< 接收緩沖區(qū)讀出指針       */


uint16_t rx_len_u16;             /**< 接收緩沖區(qū)有效數(shù)據(jù)大小   */


uint16_t tx_in_u16;              /**< 發(fā)送緩沖區(qū)寫入指針       */


uint16_t tx_out_u16;             /**< 發(fā)送緩沖區(qū)讀出指針       */


uint16_t tx_len_u16;             /**< 發(fā)送緩沖區(qū)有效數(shù)據(jù)大小   */


uint16_t rxbuf_len_u16;          /**< 接收緩沖區(qū)大小           */   


uint16_t txbuf_len_u16;          /**< 發(fā)送緩沖區(qū)大小           */


driver_can_data_t *rx_buf_pt;    /**< 接收緩沖區(qū)               */


driver_can_data_t *tx_buf_pt;    /**< 發(fā)送緩沖區(qū)               */


}driver_can_t;

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5046

    文章

    18821

    瀏覽量

    298630
  • keil
    +關(guān)注

    關(guān)注

    68

    文章

    1207

    瀏覽量

    166182
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1602

    瀏覽量

    48896
  • 結(jié)構(gòu)體
    +關(guān)注

    關(guān)注

    1

    文章

    127

    瀏覽量

    10803
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    膽機(jī)使用的注意事項(xiàng)

    膽機(jī)使用的注意事項(xiàng):膽機(jī)使用的注意事項(xiàng) 我是初哥, 現(xiàn)在對(duì)膽機(jī)感興趣, 但聽說膽機(jī)使用麻煩, 請(qǐng)問有什么需要注意的?湖南吉首火車站 范增不必?fù)?dān)心, 膽機(jī)的使用方法
    發(fā)表于 11-29 17:09 ?46次下載

    DS2746應(yīng)用注意事項(xiàng)

    DS2746應(yīng)用注意事項(xiàng) Abstract: The DS2746 has two auxiliary inputs to allow voltage sampling of resistor
    發(fā)表于 04-30 13:54 ?789次閱讀
    DS2746應(yīng)用<b class='flag-5'>注意事項(xiàng)</b>

    聚合物電池PACK操作注意事項(xiàng)

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

    電池組的設(shè)計(jì)加工注意事項(xiàng)

    電池組的設(shè)計(jì)加工注意事項(xiàng) 電池組設(shè)計(jì)注意事項(xiàng): 1、功率要求 2、體積空
    發(fā)表于 11-05 08:47 ?1479次閱讀

    硒鼓注意事項(xiàng)

    硒鼓注意事項(xiàng)     1、避免在高濕、高溫、高寒環(huán)
    發(fā)表于 12-28 15:47 ?1162次閱讀

    鉭電解應(yīng)用注意事項(xiàng)

    鉭電解應(yīng)用注意事項(xiàng) 使用電壓---------------------------------------------------------------------------------------------------------------------電容器
    發(fā)表于 03-31 15:54 ?568次閱讀

    pcb注意事項(xiàng)

    pcb注意事項(xiàng),感興趣的小伙伴們可以看看。
    發(fā)表于 07-29 17:46 ?0次下載

    Protel布線設(shè)計(jì)注意事項(xiàng)

    Protel布線設(shè)計(jì)注意事項(xiàng),好資料,下來看看。
    發(fā)表于 01-12 12:48 ?0次下載

    電源MOSFET使用注意事項(xiàng)

    關(guān)于電源MOSFET使用注意事項(xiàng)說明。
    發(fā)表于 06-18 15:22 ?24次下載

    使用pragma pack函數(shù)修改對(duì)齊方式應(yīng)用筆記

    AN1207 使用pragma pack函數(shù)修改對(duì)齊方式應(yīng)用筆記,本文介紹了如何使使用#pragma pack ()函數(shù)修改對(duì)齊
    發(fā)表于 05-10 14:46 ?1次下載
    使用<b class='flag-5'>pragma</b> <b class='flag-5'>pack</b>函數(shù)修改對(duì)齊方式應(yīng)用筆記

    COB光源的使用注意事項(xiàng)

    COB光源的使用注意事項(xiàng)
    的頭像 發(fā)表于 12-13 15:45 ?1514次閱讀

    使用注意事項(xiàng)

    使用注意事項(xiàng)
    發(fā)表于 03-17 20:14 ?1次下載
    使用<b class='flag-5'>注意事項(xiàng)</b>

    使用注意事項(xiàng)

    使用注意事項(xiàng)
    發(fā)表于 07-07 19:04 ?0次下載
    使用<b class='flag-5'>注意事項(xiàng)</b>

    RX72N組的高溫操作注意事項(xiàng)

    電子發(fā)燒友網(wǎng)站提供《RX72N組的高溫操作注意事項(xiàng).pdf》資料免費(fèi)下載
    發(fā)表于 01-29 11:21 ?0次下載
    RX72<b class='flag-5'>N</b>組的高溫操作<b class='flag-5'>注意事項(xiàng)</b>

    RX66N組的高溫操作注意事項(xiàng)

    電子發(fā)燒友網(wǎng)站提供《RX66N組的高溫操作注意事項(xiàng).pdf》資料免費(fèi)下載
    發(fā)表于 01-29 11:22 ?0次下載
    RX66<b class='flag-5'>N</b>組的高溫操作<b class='flag-5'>注意事項(xiàng)</b>