- 相關(guān)推薦
C語(yǔ)言中可移植且可靠的指針運(yùn)算
1C語(yǔ)言是目前世界上使用最為廣泛的計(jì)算機(jī)語(yǔ)言之一,目前已經(jīng)成為各大高校主要的計(jì)算機(jī)教學(xué)語(yǔ)言。下面小編為大家介紹C語(yǔ)言中可移植且可靠的指針運(yùn)算吧!
指針不是整數(shù)
指針變量包含 C 語(yǔ)言數(shù)據(jù)的地址。例如,查看以下幾行代碼。
int a, *p;
/* 為指針賦予某個(gè)目標(biāo)的地址 */
p = &a;
/* 解除引用指針以間接訪問(wèn)目標(biāo) */
*p = 0;
上面的代碼將變量a 的值設(shè)置為0。應(yīng)用到a 的&運(yùn)算符返回一個(gè)表示該變量位置的值(地址)。如果將該值復(fù)制到一個(gè)指針變量,然后對(duì)指針解除引用(使用*運(yùn)算符),則該表達(dá)式表示原始變量a。這很容易讓人認(rèn)為該地址在數(shù)值上等于變量a 所在的計(jì)算機(jī)存儲(chǔ)器地址,但在C 語(yǔ)言中并沒(méi)有此類要求。
以下示例可清楚地說(shuō)明最后一點(diǎn):考慮具有多個(gè)獨(dú)立存儲(chǔ)區(qū)的PIC 器件。對(duì)位于數(shù)據(jù)存儲(chǔ)器中器件地址100h 的變量使用地址運(yùn)算符時(shí)應(yīng)返回什么值?而對(duì)位于程序存儲(chǔ)器中器件地址100h 的另一個(gè)變量使用地址運(yùn)算符時(shí)又應(yīng)返回什么值?
如果在兩種情況下都回答 100h,那么在運(yùn)行時(shí)如何得知100h 是數(shù)據(jù)存儲(chǔ)器中的地址還是程序存儲(chǔ)器中的地址呢?顯然,在這種情況下,如果稍后要解除引用地址,則需要其他方式來(lái)確定應(yīng)訪問(wèn)哪個(gè)存儲(chǔ)器。
“其他方式”可以是對(duì)地址運(yùn)算符返回的值進(jìn)行特殊編碼(與MPLAB XC8 編譯器配合使用的技術(shù)),也可以使用傳達(dá)相同信息的特殊指針類型限定符(MPLAB XC16 和XC32 編譯器使用該方法)。
為保持代碼的可移植性,不應(yīng)假設(shè)將整數(shù)賦給指針就會(huì)使指針能訪問(wèn)任何對(duì)象,即使該整數(shù)的值與某個(gè)對(duì)象的器件地址相同。因此對(duì)于上面的示例,為指針賦值立即數(shù)100h(或者保留此值的整數(shù)變量)并不意味著該指針指向變量a。
/* 我們發(fā)現(xiàn)“a”被分配到地址100h
*/int a, *p;
/* 注:這涉及整數(shù)到指針的隱式轉(zhuǎn)換 */
p = 0x100;
/* 沒(méi)人知道會(huì)發(fā)生什么!*/
*p = 0;
請(qǐng)記住,一種地址空間中的取指和存儲(chǔ)可能不像另一種地址空間中的取指和存儲(chǔ)一樣簡(jiǎn)單——編譯器可能需要使用不同的寄存器和指令才能執(zhí)行訪問(wèn)。
基于同樣的原因,在定義指針時(shí),必須使用適當(dāng)?shù)闹羔橆愋拖薅ǚ。由?MPLAB XC8 對(duì)地址進(jìn)行編碼,因此它不使用特殊地址空間限定符,而MPLAB XC16 和XC32 則使用。但是,兩種情況下都必須適時(shí)使用通常的const 和volatile 限定符。限定符在數(shù)據(jù)定義中指定,如果想要可靠地訪問(wèn)該數(shù)據(jù),則需要使用與引用該數(shù)據(jù)的指針相匹配的限定符。例如,使用MPLABXC16 時(shí):
__psv__ char buffer[8] __attribute__((space(psv)))
在閃存程序存儲(chǔ)器中放置一個(gè)字符數(shù)組buffer,可通過(guò)“psv”(程序空間可視性)窗口進(jìn)行訪問(wèn)。直接訪問(wèn)buffer 將使編譯器生成可確保psv 窗口(位于處理器地址空間中的特定位置)映射到閃存(包含“buffer”)中適當(dāng)位置的代碼。buffer 的“地址”是所需窗口設(shè)置與“buffer”在整個(gè)窗口中的可視區(qū)域內(nèi)的偏移量的組合。
通過(guò)指針引用“buffer”中的項(xiàng)時(shí),必須使用如下指針:
__psv__ char *bp;
才能使編譯器生成正確的代碼。不帶__psv__限定符的“普通”指針不起作用。
因此指針不僅僅是一個(gè)寬到可以保存“地址”的整數(shù),它還具有關(guān)聯(lián)的目標(biāo)類型;C 語(yǔ)言數(shù)據(jù)地址不僅僅是一個(gè)計(jì)算機(jī)存儲(chǔ)器地址,它可由編譯器修改或優(yōu)化。C 編譯器還會(huì)考慮其他一些事項(xiàng)。
出問(wèn)題的位置
如果我們認(rèn)為指針只是一個(gè)值為(計(jì)算機(jī)存儲(chǔ)器)地址的整數(shù),并且認(rèn)為我們已了解地址的含義以及該存儲(chǔ)器中排列數(shù)據(jù)的方式,我們可能會(huì)想要在所編寫(xiě)的C 語(yǔ)言代碼中顯式執(zhí)行各種各樣的地址運(yùn)算,進(jìn)而在程序中嵌入底層運(yùn)行時(shí)環(huán)境的特定于實(shí)現(xiàn)的詳細(xì)信息。這樣一來(lái),即使現(xiàn)在程序可以運(yùn)行,但如果針對(duì)其他處理器進(jìn)行編譯,可能就無(wú)法正常工作,或者可能在看起來(lái)無(wú)關(guān)緊要的更改后莫名停止工作。我們?cè)撊绾伪苊膺@類問(wèn)題呢?
1. 使用正確的指針類型。根據(jù)引用的數(shù)據(jù)選擇適用的指針類型。盡管在你添加一系列轉(zhuǎn)換后程序會(huì)進(jìn)行編譯,但不要據(jù)此認(rèn)為程序會(huì)實(shí)際按照你的期望工作。它會(huì)按照你告訴它的方式工作,這可能與你的期望有很大不同。
2. 根據(jù)你將用來(lái)訪問(wèn)數(shù)據(jù)的結(jié)構(gòu)來(lái)分配數(shù)據(jù)
3. 不要猜測(cè)數(shù)據(jù)類型的布局
例如,可以分配一個(gè)字符緩沖區(qū),然后將該緩沖區(qū)的地址轉(zhuǎn)換為指向更大類型數(shù)據(jù)數(shù)組或結(jié)構(gòu)數(shù)組的指針。隨后你可能會(huì)通過(guò)不同類型的指針,有時(shí)訪問(wèn)字符型數(shù)據(jù),有時(shí)訪問(wèn)其他類型的數(shù)據(jù)。為此,必須知道更大類型的數(shù)據(jù)在字符數(shù)據(jù)上以及彼此之間的排列方式。這非常危險(xiǎn)而且容易出錯(cuò)。如果需要通過(guò)多類型“視圖”訪問(wèn)數(shù)據(jù),請(qǐng)將數(shù)據(jù)分配成聯(lián)合數(shù)組,然后通過(guò)聯(lián)合訪問(wèn)數(shù)據(jù)。編譯器將清楚你的意圖并幫助你正確實(shí)現(xiàn)。
示例
下面的 C 程序建立了一個(gè)初始化結(jié)構(gòu)數(shù)組,顯示該數(shù)組,修改數(shù)組的一個(gè)元素,最后顯示更新的結(jié)果。代碼中針對(duì)選擇和更新要更改的元素提供了幾種備選方法。其中一些是常用方法,但實(shí)際上是不安全的代碼模式:
1: /* 用于演示指針運(yùn)算問(wèn)題的測(cè)試程序 */
2: #include
3: #include
4:
5: struct twoints {
6: uint8_t a;
7: uint32_t b;
8: };
9:
10: static struct twoints twointbuf[4] = {
11: {1, 5}, {2, 6}, {3, 7}, {4, 8}
12: };
13:
14: int main(int argc, char *argv[])
15: {
16: struct twoints *p;
17: size_t i;
18:
19: /* 輸出結(jié)構(gòu)數(shù)組 */
20: printf(“Before: ”);
21: i = 0;
22: p = twointbuf;
23: while (i < 4) {
24: printf(“0x%02x , 0x%08x ”, p->a, (*p).b);
25: ++p;
26: ++i;
27: }
28: printf(“ ”);
29:
30: /* 選擇下標(biāo)為2 的元素的正確方法 */
31: p = twointbuf + 2;
32:
33: /* 等效且同樣好的方法 */
34: #ifdef ALSORIGHT
35: p = &twointbuf[2];
36: #endif
37:
38: /* 正確,但沒(méi)有必要采用的方法 */
39: #ifdef CORRECTBUTWHY
40: p = (struct twoints *)((char *)twointbuf + 2*sizeof(struct twoints));
41: #endif
42:
43: /* 以下是常見(jiàn)錯(cuò)誤 */
44: #ifdef REALLYWRONG
45: p = (struct twoints *)((char *)twointbuf + 2*(sizeof(uint8_t) + sizeof(uint32_t)));
46: #endif
47: #ifdef NOTSAFE
48: p = (struct twoints *)((size_t)twointbuf + 2*sizeof(struct twoints));
49: #endif
50:
51: /* 修改元素2 */
52: p->b = 0xffffffff;
53:
54: /* 顯示更新的數(shù)組 */
55: printf(“After: ”);
56: i = 0;
57: p = &twointbuf[0];
58: while (i < 4) {
59: printf(“0x%02x , 0x%08x ”, (p + i)->a,(p[i]).b);
60: ++i;
61: }
62: printf(“ ”);
63:
64: return 0;
65: }
我們討論一下如何訪問(wèn)要修改的第二個(gè)結(jié)構(gòu)元素。在第10 行中聲明的twointbuf 是一個(gè)結(jié)構(gòu)數(shù)組,相當(dāng)于指向該數(shù)組首地址的指針。我們可以通過(guò)數(shù)組或指針語(yǔ)法來(lái)訪問(wèn)該數(shù)組中的元素,這兩種編碼風(fēng)格表示同一個(gè)意思。第31 行和第35 行中給出的備選方法均是獲取指向數(shù)組中元素2 的指針的安全方法。編譯器不會(huì)將“2”解讀成兩個(gè)字節(jié)或兩個(gè)“字”,而是解讀成元素0 和元素1 后面的元素的編號(hào)2。
在第 40 行,我們看到了根據(jù)數(shù)組的字節(jié)地址以及前面元素的長(zhǎng)度(字節(jié))來(lái)計(jì)算結(jié)構(gòu)元素地址的示例。如果(char *)上的限定符與數(shù)組上的限定符(本示例中沒(méi)有)匹配,則這種方法可行——只要字符指針和數(shù)組均聲明為引用相同的地址空間,地址和增量映射到底層存儲(chǔ)的規(guī)則就會(huì)相同,且該代碼有效。但為什么要這樣做呢?使用C 語(yǔ)言提供的簡(jiǎn)潔明了的語(yǔ)法,編譯器將生成同樣正確或更有效的代碼。
在第 45 行,此代碼假設(shè)結(jié)構(gòu)元素的長(zhǎng)度(字節(jié))是兩個(gè)成員的長(zhǎng)度之和。這是不安全的假設(shè),因?yàn)榫幾g器可能必須對(duì)結(jié)構(gòu)進(jìn)行填充才能使兩個(gè)成員在自然字邊界上對(duì)齊。是否使用結(jié)構(gòu)填充將取決于目標(biāo)器件。
第 48 行上的語(yǔ)句一開(kāi)始沒(méi)有將數(shù)組指針轉(zhuǎn)換為字符指針,而是轉(zhuǎn)換為大到足以保存指針的整數(shù),從而向編譯器隱藏了該值是特定地址空間中的地址的事實(shí)。隨后執(zhí)行與第40 行相同的地址運(yùn)算,并將結(jié)果轉(zhuǎn)換回指向結(jié)構(gòu)數(shù)組的指針。在這種情況下,編譯器沒(méi)有機(jī)會(huì)對(duì)添加為特定空間中的指針和下標(biāo)的數(shù)字進(jìn)行解讀,且無(wú)法應(yīng)用任何映射規(guī)則。因此轉(zhuǎn)換回結(jié)構(gòu)指針的值可能是錯(cuò)誤的。
結(jié)論
使用C 語(yǔ)言的功能時(shí),應(yīng)依據(jù)功能在語(yǔ)言中的含義:
使用地址運(yùn)算符來(lái)獲取要賦給指針的地址。
確保所定義的指針類型在程序執(zhí)行期間與其可引用的數(shù)據(jù)相匹配。
決不要假設(shè)對(duì)象分配到存儲(chǔ)器的方式。
不要假設(shè)或回避規(guī)則來(lái)使 C語(yǔ)言代碼更“直接”和“有效”,此類代碼不會(huì)具有可移植性、可靠性或更有效。
【C語(yǔ)言中可移植且可靠的指針運(yùn)算】相關(guān)文章:
C語(yǔ)言中的指針解讀11-01
什么是C語(yǔ)言中指針 C語(yǔ)言指針的基礎(chǔ)使用10-01
C語(yǔ)言指針變量的運(yùn)算10-31
C語(yǔ)言中指針的概念03-16
C語(yǔ)言中的指針是什么08-08
C語(yǔ)言中的指針指什么08-23
C語(yǔ)言中野指針的深入解析08-06