void使用詳解

 1.空指針

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

    爲什麼操作系統不是保留一個字節呢?由於內存管理是按頁來進行的,因此無法做到單獨保留一個字節。儘管如此,但還是有極少數系統設定RAM區從0地址開始,但指向有效變量的指針不會指向0地址。即使“代碼區”從0地址開始,但在任何情況下,0地址都不是C語言中任何函數的起始地址,因此指向有效函數地址的指針也不會指向0地址。

    毫無疑問,任何一種指針類型都有一個特殊的指針值,即空指針。它既不會指向任何對象或函數,也不是任何對象或函數的地址。而未初始化的指針,則完全可能指向任何地方。

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

 

2. 空指針常量與NULL

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

    爲了讓程序中的空指針使用更加明確,標準C專門定義了一個標準預處理宏NULL,其值爲“空指針常量”,通常爲0或(void *)0,即在指針上下文中NULL與0是等價的,而未加修飾的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'

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

 

3. 空指針的用途

一般來說,未初始化是不能使用的非法指針,因爲它完全有可能指向任何地方,從而導致程序無法判斷它爲非法指針。因此,不管指針變量是全局的還是局部的、靜態的還是非靜態的,都應該在聲明它的同時進行初始化,要麼賦予一個有效的地址,要麼賦予NULL。

標準C規定,全局指針變量的默認值爲NULL,而對於局部指針變量則必須明確地指定其初值。因此,void通常用於指針變量的初始化,用來判斷一個指針的有效性。比如:

         unsigned char *pucBuf=(void *)0;    // 定義pucBuf爲unsigned char類型指針並初始化爲空指針

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

         if(pucBuf==0){

            return error;                     // 如果pucBuf爲空指針,則返回參數錯誤

         }

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

         unsignned char *pcData = NULL;      // 定義pcData爲unsigned char類型指針

         void           *pvData;             // 定義pvData爲void類型指針

        

         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排序函數
  void swap (void *pvData1, void *pvData2, int iDataSize)
  {
     unsigned char  *pcData1 = NULL;
     unsigned char  *pcData2 = NULL;
     unsigned char  ucTmp1;
 
     pcData1 = (unsigned char *)pvData1;
     pcData2 = (unsigned char *)pvData2;
      do {
          ucTmp1       = *pcData1;
          *pcData1     = *pcData2;
          *pcData2     = ucTmp1;
          pcData1++;
          pcData2++;
      } while (--iDataSize > 0);

 }           


 

 

 

發佈了21 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章