1. 程式人生 > >深入理解void型別

深入理解void型別

1.空指標

    一般來說,程式的起始地址是從“程式碼區”的0地址開始存放的(注:如果插入一個記憶體分佈圖,則更能說明問題,此處省略),但實際上現代作業系統並非如此,卻保留了從0開始的一塊記憶體。至於這塊記憶體到底有有多大,與具體的作業系統有關。如果程式試圖訪問這塊記憶體,則系統提示異常。

    為什麼作業系統不是保留一個位元組呢?由於記憶體管理是按頁來進行的,因此無法做到單獨保留一個位元組。儘管如此,但還是有極少數系統設定RAM區從0地址開始,但指向有效變數的指標不會指向0地址。即使“程式碼區”從0地址開始,但在任何情況下,0地址都不是C語言中任何函式的起始地址,因此指向有效函式地址的指標也不會指向

0地址。

       ☛ 課外知識延伸

    雖然 80C51微控制器XDATA區(外部RAM)是從0地址開始的,但只要對儲存在0地址中的變數不進行取地址操作(&操作),即可有效地保證指標不會指向0地址。

    與此同時,雖然32ARM7微控制器也是從0地址開始的,但這塊記憶體僅用於存放中斷向量程式碼,而不是程式中的有效變數地址,因此即便用空指標來判斷指標的有效性,其仍然是可行的。

    基於此,於是將空指標定義為指向0地址的指標。毫無疑問,任何一種指標型別都有一個特殊的指標值,即空指標。它既不會指向任何物件或函式,也不是任何物件或函式的地址。而未初始化的指標,則完全可能指向任何地方。

    由此可見,空指標與未初始化的指標是完全不同的兩個概念。那麼,將如何在程式中獲得一個空指標呢?

2. 空指標常量與NULL

標準C規定,在初始化、賦值或比較時,如果一邊是變數或指標型別的表示式,則編譯器可以確定另一邊的常數0為空指標,並生成正確的空指標值。即在指標上下文中“值為0的整型常量表達式”在編譯時轉換為空指標。

為了讓程式中的空指標使用更加明確,標準C專門定義了一個標準預處理巨集NULL其值為“空指標常量”,通常為0(void *)0,即在指標上下文中NULL0是等價的,而未加修飾的0也是完全可以接受的。由於void *指標的特殊賦值屬性,比如:

#define NULL ((void *)0)

NULL定義為((void *)0)NULL是可以賦值給任何型別指標的值它的型別為void*,而不是整數0,因此初始化FILE *fp = NULL;是完全合法的。

而為了區分整數0和空指標0,當需要其它型別的0的時候,即使可能工作,但也不能使用NULL,如果這樣處理其格式是錯誤的,這在非指標上下文中是不能工作的。特別地,不能在需要ASCII空字元(NUL)的地方使用NULL。如果確實需要,則可以自定義為:

#define NUL ''

由此可見常數0是一個空指標常量NULL僅僅是它的一個別名。

3. 空指標的用途

一般來說,未初始化是不能使用的非法指標,因為它完全有可能指向任何地方,從而導致程式無法判斷它為非法指標。因此,不管指標變數是全域性的還是區域性的、靜態的還是非靜態的,都應該在宣告它的同時進行初始化,要麼賦予一個有效的地址,要麼賦予NULL

標準C規定,全域性指標變數的預設值為NULL,而對於區域性指標變數則必須明確地指定其初值。因此void通常用於指標變數的初始化,用來判斷一個指標的有效性。比如

unsigned char *pucBuf=(void *)0;// 定義pucBufunsigned char型別指標並初始化為空指標

如果後續的程式碼忘記初始化指標而直接使用的話,則可能造成程式失敗。雖然空指標也是非法指標,但可以通過程式判斷並告訴程式設計師程式碼可能有問題。也就是說,如果一開始就將指標初始化為空指標,則可避免程式異常。比如:

if(pucBuf==0){

return error; // 如果pucBuf為空指標,則返回引數錯誤

}

由於void型別指標的不確定性,因此它可以指向任意型別的資料,那麼只要在使用時做一個簡單的強制型別轉換就可以了。比如:

unsignned char*pcData = NULL;// 定義pcDataunsigned char型別指標

void*pvData;// 定義pvDatavoid型別指標

pvData = pcData;// 無需進行強制型別轉換

pcData = (unsigned char*) pvData;// pvData強制轉換為unsigned char型別指標

顯然不存在void型別的物件也就是說當物件為空型別時其大小為0位元組當物件未確定型別時那麼它的大小也是未確定的因此不能宣告void型別變數。比如:

void a; // 非法宣告

既然上述宣告是非法的那麼也就不能將sizeof運算子用於void型別。也就意味著,編譯器不知道所指物件的大小,由於指標的算術運算總是基於所指物件的大小的,因此不允許對void指標進行算術運算。

總之,在指標宣告中,void *表示通用指標的型別。如果void作為函式的返回型別,則表示不返回任何值。如果void位於引數列表中,則表示沒有引數。

4. 用無型別指標作為函式引數

    由於C語言中最小長度的變數為char型別(包括unsigned char、signed char等),其sizeof(char)的結果為1,而其它任何變數的長度都是它的整數倍。比如,如果使用SDCC51編譯器,其sizeof(int)為2。因為通用swap函式函式不知道需要交換的變數的型別,所以需要一個引數給出相應的指示。由於C語言的變數型別多種多樣,因此不可能為每一種變數型別編號,而且swap並不關心變數的真正型別,所以可以用變數的長度代替變數型別。通用swap函式的原型為:

void swap(void *pvData1, void *pvData2, int iDataSize)

將a,b兩個變數(變數型別必須一樣)的值交換的程式碼如下:

swap(&a, &b, sizeof(a));

通用swap排序函式的參考程式碼見程式清單1.1。

程式清單1.1  通用swap排序函式

1   void swap (void *pvData1, void *pvData2, int iDataSize)

2   {

3       unsigned char  *pcData1 = NULL;

4       unsigned char  *pcData2 = NULL;

5       unsigned char  ucTmp1;

6      

7       pcData1 = (unsigned char *)pvData1;

8       pcData2 = (unsigned char *)pvData2;

9      

10      do {

11          ucTmp1       = *pcData1;

12          *pcData1     = *pcData2;

13          *pcData2     = ucTmp1;

14          pcData1++;

15          pcData2++;

16      } while (--iDataSize > 0);

17  }