指針,是C語(yǔ)言中的一個(gè)重要概念及其特點(diǎn),也是掌握C語(yǔ)言比較困難的部分。指針也就是內(nèi)存地址,指針變量是用來(lái)存放內(nèi)存地址的變量。
本質(zhì)還是一個(gè)變量,指針提供了一種對(duì)存儲(chǔ)位置的動(dòng)態(tài)訪問(wèn)手段,(相對(duì)于普通變量而言,普通變量只能訪問(wèn)自己所占的存儲(chǔ)位置)
內(nèi)存地址對(duì)齊,是計(jì)算機(jī)在內(nèi)存中的數(shù)據(jù)排列、訪問(wèn)數(shù)據(jù)的方式,包含了基本數(shù)據(jù)對(duì)齊和結(jié)構(gòu)體數(shù)據(jù)對(duì)齊的兩種相互獨(dú)立又相互關(guān)聯(lián)的部分。
現(xiàn)代計(jì)算機(jī)在內(nèi)存中讀寫數(shù)據(jù)是按字節(jié)塊進(jìn)行操作,理論上任意類型的變量訪問(wèn)可以從任何地址開(kāi)始,但是計(jì)算機(jī)系統(tǒng)對(duì)任意數(shù)據(jù)類型在內(nèi)存中存放位置有限,它會(huì)要求這些數(shù)據(jù)的首地址的值為K(4位或者8位)的整數(shù)倍。
如何踩坑的?
在一份十分優(yōu)秀的代碼中,指針的使用率占比很高,因?yàn)橹羔樐茏尨a實(shí)現(xiàn)變得更自由、更高效和更方便等諸多優(yōu)點(diǎn),可對(duì)于不十分熟悉指針的朋友來(lái)說(shuō),用起來(lái)也許就是災(zāi)難(常見(jiàn)的就是程序跑飛)
因此,通過(guò)指針的使用率大概就能判斷一個(gè)人的編程能力水平
請(qǐng)看下面的代碼,運(yùn)行結(jié)果是怎么樣的呢?
//?假設(shè)數(shù)組首地址為?0x00004000,符合內(nèi)存對(duì)齊:4的倍數(shù)
static?unsigned?char?sg_arrBuf[100];
int?main()
{
memset(sg_arrBuf,?0,?sizeof(sg_arrBuf));
//?地址為?0x00004000
uint8_t?*pucVal?=?(uint8_t?*)&sg_arrBuf[0];
//?地址為?0x00004001
uint16_t?*puiVal?=?(uint16_t?*)&sg_arrBuf[1];
*pucVal?=?20;???//?HEX:?0x14
*puiVal?=?2000;?//?HEX:?0x07d0
printf("HEX:?");
for?(int?i?=?0;?i?<?3;?i++)
{
printf("%02x?",?sg_arrBuf[i]);
}
printf("n");
return?0;
}
很多朋友期望的結(jié)果如下(小端模式):
HEX:?14?d0?07
事實(shí)真的一定如此嗎?
不一定!
也許部分朋友在自己電腦上打開(kāi) VS 復(fù)制粘貼運(yùn)行了,編譯后運(yùn)行,結(jié)果還真和上面一樣?。?!你這不是在忽悠人嗎?。。?/p>
那有試過(guò)在 MCU 上跑過(guò)嗎?是不是程序跑飛了?
為什么?
當(dāng)計(jì)算機(jī)讀取或?qū)懭雰?nèi)存地址時(shí),它將以字(word)大小的塊進(jìn)行存儲(chǔ)。數(shù)據(jù)對(duì)齊意味著將數(shù)據(jù)放在等于字長(zhǎng)的倍數(shù)的內(nèi)存偏移處,正是由于這種CPU處理內(nèi)存的方式從而提高了系統(tǒng)的性能。大多數(shù)CPU只能訪問(wèn)內(nèi)存對(duì)齊的地址。
意味著部分系統(tǒng)架構(gòu)體系對(duì)于未對(duì)齊的地址進(jìn)行訪問(wèn)時(shí)會(huì)發(fā)生異常,如果嘗試去訪問(wèn)內(nèi)存未對(duì)齊的變量進(jìn)行操作會(huì)導(dǎo)致總線錯(cuò)誤。它只支持對(duì)齊訪問(wèn)。
比如我們?cè)L問(wèn)一個(gè) 4 字節(jié) (Double Word) 型的變量時(shí),如果這個(gè)變量的起始地址是能被 4 整除的話,我們說(shuō)這種訪問(wèn)是雙字節(jié)對(duì)齊的。如果訪問(wèn)一個(gè) 2 字節(jié) ( Word ) 變量,當(dāng)起始地址能被 2 整除時(shí)是對(duì)齊的。訪問(wèn)字節(jié) ( Byte ) 型變量,總是對(duì)齊的。
根據(jù)這個(gè)就能很快鎖定問(wèn)題原因了,那就是程序運(yùn)行到這個(gè)位置就導(dǎo)致總線錯(cuò)誤,從而跑飛了。
//?地址為?0x00004001,未對(duì)齊
*puiVal?=?2000;?//?HEX:?0x07d0
預(yù)防及解決措施
關(guān)于上述的寫法,有些朋友可能在電腦端實(shí)現(xiàn)了某個(gè)功能,電腦測(cè)試沒(méi)有任何問(wèn)題,但是一旦移植到 MCU 上就不行,那么抓緊檢查一下是不是這個(gè)問(wèn)題呢。
因此,為了確保我們的代碼有很高的移植性和穩(wěn)定性,那么一定要預(yù)防這種情況,可以通過(guò)采用訪問(wèn)字節(jié) ( Byte ) 型變量,也可以使用memcpy的方式處理這種操作就能避免這種問(wèn)題。
//?假設(shè)數(shù)組首地址為?0x00004000,符合內(nèi)存對(duì)齊: 4的倍數(shù)
static?unsigned?char?sg_arrBuf[100];
int?main()
{
memset(sg_arrBuf,?0,?sizeof(sg_arrBuf));
uint8_t?ucVal?=?20;
uint16_t?uiVal?=?2000;
memcpy(&sg_arrBuf[0],?&ucVal,?1);
memcpy(&sg_arrBuf[1],?&uiVal,?2);
printf("HEX:?");
for?(int?i?=?0;?i?<?3;?i++)
{
printf("%02x?",?sg_arrBuf[i]);
}
printf("n");
return?0;
}