加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 布置場景
    • 問題是怎么發(fā)生的?
    • 原因是什么呢?
    • 如何處理呢?
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

C語言為什么使用指針會(huì)導(dǎo)致死機(jī)?

06/26 08:50
1915
閱讀需 10 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

STM32系列的M0內(nèi)核剛出來的時(shí)候,為了降本,我有很多項(xiàng)目都需要移植到M0平臺(tái),本以為只是把底層驅(qū)動(dòng)看一下就可以,卻不想總是遇到莫名其妙的死機(jī),著實(shí)折騰過一段時(shí)間的。今天在DIY遙控器的時(shí)候,突然又發(fā)現(xiàn)了一個(gè)當(dāng)時(shí)遇到的問題,記錄下來給大家提個(gè)醒,萬一遇到的死機(jī)的情況,不妨思考一下這個(gè)問題。

布置場景

因?yàn)槲业倪b控器是基于無線RF通信的,因此我需要定義了兩個(gè)幀結(jié)構(gòu),分別用數(shù)組來存放,然后再發(fā)送和接收函數(shù)中直接處理數(shù)組。幀結(jié)構(gòu)的前四個(gè)字節(jié)作為通信的地址,也就是一個(gè)過濾ID,不符合這個(gè)ID的幀直接放棄掉。為了讓系統(tǒng)中兼容更多的設(shè)備對,我把這個(gè)通信地址定義為4個(gè)字節(jié),也就是一個(gè)uint32_t類型。數(shù)組及其索引值定義如下:

// 接收緩沖區(qū)長度定義#define DW_RX_MSG_LEN           18// 接收包中的地址索引#define    RX_ADDRESS_LL           0#define    RX_ADDRESS_LH           1#define    RX_ADDRESS_HL           2#define    RX_ADDRESS_HH           3...
uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0};  //接收緩存定義

接收緩沖器如上定義完成后,我將在接收函數(shù)中處理這個(gè)接收到的幀,第一件事情肯定是比對地址是否匹配,假設(shè)我宏定義的地址為:

#define  ADDRESS  0x12345678

最簡單的寫法肯定是這樣判斷:

if((g_dw_rx_msg[RX_ADDRESS_LL] == (ADDRESS >> 0) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_LH] == (ADDRESS >> 8) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HL] == (ADDRESS >> 16) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HH] == (ADDRESS >> 24) & 0xFF)    ){
}

上面這種寫法很明了,我們比對每一個(gè)字節(jié)是否相等,如果都相等,我們認(rèn)為這個(gè)幀是發(fā)送給我們的,但是這樣的寫法比較啰嗦,字?jǐn)?shù)比較多,并且并不直觀呀,好好的一個(gè)32bit的數(shù)據(jù),非要拆分成4次比較,干起來效率一點(diǎn)都不高。

問題是怎么發(fā)生的?

C語言中一定要用指針才顯得高級,這里很明顯可以使用指針來取出數(shù)組的前四個(gè)字節(jié),然后和我們的宏定義地址相比較,這樣是非常簡單高效的,大牛們常常都是這么寫的。

if(*(uint32_t *)g_dw_rx_msg == ADDRESS){}

你看多簡單,數(shù)組的名稱就是數(shù)組的地址,也是第一個(gè)元素的地址,我們?nèi)〕鰜磙D(zhuǎn)換成32bit整型,然后取出來直接和我們的地址比較。既能清楚的表達(dá)這是一個(gè)地址的比對,又能省不少敲字符的力氣。這樣也許在CortexM3上面大概率不會(huì)出現(xiàn)問題,但是在M0內(nèi)核上大概率會(huì)出問題,會(huì)死機(jī)的。為什么總說是概率呢?我先來描述一下你可能遇到的現(xiàn)象。我按照指針的寫法編譯通過了,運(yùn)行也沒有問題,性能杠杠的,賊穩(wěn)定。后來,突然增加了一個(gè)需求,于是我又定義了一些變量和數(shù)組,再編譯,運(yùn)行….

我擦,死機(jī)了!我就一直在新增的功能那里不斷地查找問題,怎么也找不到那里寫的有問題,只要我新定一個(gè)變量,并且在程序中使用它(防止編譯器優(yōu)化掉),就會(huì)死機(jī),去掉就沒事了。調(diào)試過程中,你一定遇到過這樣的場景。當(dāng)我們Debug一步一步的跟蹤時(shí),就會(huì)發(fā)現(xiàn),程序死機(jī)之前執(zhí)行了我們的判斷語句。

原因是什么呢?

我們都知道,CortexM0內(nèi)核是一個(gè)32位總線的,它的硬件無法處理32位未對齊的內(nèi)存訪問,那么他對于內(nèi)存的訪問地址一定是4的整數(shù)倍,否則就會(huì)出現(xiàn)無法訪問內(nèi)存的問題。這里,我們的判斷語句中使用了指針的強(qiáng)制類型轉(zhuǎn)換(uint3_t*),那么如果我們數(shù)組中的元素地址恰好不是4的整數(shù)倍的時(shí)候,在轉(zhuǎn)化成uint3_t進(jìn)行訪問時(shí),處理器就只能報(bào)錯(cuò)了。

為什么出錯(cuò)是概率性的呢?編譯器會(huì)根據(jù)我們定義的全局變量和靜態(tài)變量來安排變量的地址,并且數(shù)組這東西,如果定義成uint8_t類型的,他中間的某個(gè)元素地址對齊的可能性只有四分之一。也就是說,我們第一次編譯成功,運(yùn)行可靠的時(shí)候,恰好我們定義的全局變量合適,編譯器把我們的數(shù)組起始地址正好安排在32位對齊的地址上。當(dāng)我們再定義一個(gè)變量的時(shí)候,一定是定義了非32位對齊的,比如uint8_t,或者一個(gè)數(shù)組或者結(jié)構(gòu)體,總之,它不是4字節(jié)的整數(shù)倍。這樣,我們再編譯,編譯器就會(huì)重新給我們安排所有變量的地址,我們的數(shù)組的地址就恰好給擠到了一個(gè)地址不對齊的地方。我們再用32bit的指針類型去訪問時(shí),就會(huì)死機(jī)了。

如何處理呢?

有三種方法處理。第一種,要承認(rèn)傻人有傻福,笨辦法有笨辦法的優(yōu)點(diǎn),有些事情投機(jī)取巧可能會(huì)賺,但大概率還是賠的。就好比我們的大A,你以為是投資,后來認(rèn)為是投機(jī),再后來,你以為是詐騙,現(xiàn)在你可能認(rèn)為是搶劫了,其實(shí),我們的大A本質(zhì)上是捐款!跑題了,所以,我們老老實(shí)實(shí)按字節(jié)比較就可以了。

if((g_dw_rx_msg[RX_ADDRESS_LL] == (ADDRESS >> 0) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_LH] == (ADDRESS >> 8) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HL] == (ADDRESS >> 16) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HH] == (ADDRESS >> 24) & 0xFF)    ){
}

第二種,如果你實(shí)在忍受不了這個(gè)樣式,想讓程序更直觀的體現(xiàn)出來,它是一個(gè)地址的比對,那么就自己寫一個(gè)函數(shù),將四個(gè)字節(jié)取出來組成一個(gè)32bit的整型數(shù)。或者,你可以直接利用memcpy函數(shù)來實(shí)現(xiàn)。

uint32_t my_address = 0;memcpy(&my_address, &((uint32_t*)g_dw_rx_msg), sizeof(uint32_t));

第三種,如果你還是糾結(jié)于優(yōu)雅和效率,那我們就不撞南墻不回頭,編譯器自身提供了一些指令,來強(qiáng)制我們定義的變量地址對齊。比如:__attribute__((aligned(4))) ?如下定義:

__attribute__((aligned(4))) uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0};  //接收緩存

按照上面的定義方法,我們的數(shù)組其實(shí)地址就會(huì)被安排在32位地址對齊的位置上了,指針就可以大顯身手了,不過要注意,如果我們的地址索引在數(shù)組內(nèi)部不對齊也是不行的。比如,我們的地址是從數(shù)組的第1個(gè)地址開始的。(前面有一個(gè)第0個(gè))

// 接收緩沖區(qū)長度定義#define DW_RX_MSG_LEN           18// 接收包中的地址索引#define    RX_ADDRESS_LL           1     //這里地址從數(shù)組的第1個(gè)元素開始#define    RX_ADDRESS_LH           2#define    RX_ADDRESS_HL           3#define    RX_ADDRESS_HH           4...
__attribute__((aligned(4))) uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0};  //接收緩存
if(*(uint32_t *)g_dw_rx_msg == ADDRESS){    // 我還得死?。。。?!}

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險(xiǎn)等級 參考價(jià)格 更多信息
HFBR-1414Z 1 Broadcom Limited Transmitter, 792nm Min, 865nm Max, 160Mbps, ST Connector, DIP, Panel Mount, Through Hole Mount, ROHS COMPLIANT PACKAGE
$27.48 查看
7XZ-32.768KDA-T 1 TXC Corporation Oscillator, 0.032768MHz Nom,
$1.84 查看
CY7C1041GN30-10ZSXI 1 Cypress Semiconductor Standard SRAM, 256KX16, 10ns, CMOS, PDSO44, LEAD FREE, TSOP2-44
$6.86 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

多年硬件從業(yè)經(jīng)驗(yàn),專注分享從研發(fā)到供應(yīng)鏈,再到精益制造過程中的經(jīng)驗(yàn)和感悟!