c語言入門-C語言空指針NULL以及void指針


空指針 NULL

一個指針變量可以指向計算機中的任何一塊內存,不管該內存有沒有被分配,也不管該內存有沒有使用權限,只要把地址給它,它就可以指向,C語言沒有一種機制來保證指向的內存的正確性,程序員必須自己提高警惕。

很多初學者會在無意間對沒有初始化的指針進行操作,這是非常危險的,請看下面的例子:

1 #include <stdio.h>
2 int main(){
3     char *str;
4     gets(str);
5     printf("%s\n", str);
6     return 0;
7 }

這段程序沒有語法錯誤,能夠通過編譯和鏈接,但當用戶輸入完字符串並按下回車鍵時就會發生錯誤,在 Linux 下表現為段錯誤(Segment Fault),在 Windows 下程序直接崩潰。如果你足夠幸運,或者輸入的字符串少,也可能不報錯,這都是未知的。

前面我們講過,未初始化的局部變量的值是不確定的,C語言並沒有對此作出規定,不同的編譯器有不同的實現,我曾警告大家不要直接使用未初始化的局部變量。上面的代碼中,str 就是一個未初始化的局部變量,它的值是不確定的,究竟指向哪塊內存也是未知的,大多數情況下這塊內存沒有被分配或者沒有讀寫權限,使用 gets() 函數向它里面寫入數據顯然是錯誤的。

我強烈建議對沒有初始化的指針賦值為 NULL,例如:

char *str = NULL;

NULL 是“零值、等於零”的意思,在C語言中表示空指針。從表面上理解,空指針是不指向任何數據的指針,是無效指針,程序使用它不會產生效果。

注意區分大小寫,null 沒有任何特殊含義,只是一個普通的標識符。

很多庫函數都對傳入的指針做了判斷,如果是空指針就不做任何操作,或者給出提示信息。更改上面的代碼,給 str 賦值 NULL,看看會有什么效果:

1 #include <stdio.h>
2 int main(){
3     char *str = NULL;
4     gets(str);
5     printf("%s\n", str);
6     return 0;
7 }

運行程序后發現,還未等用戶輸入任何字符,printf() 就直接輸出了(null)。我們有理由據此推斷,gets() 和 printf() 都對空指針做了特殊處理:

  • gets() 不會讓用戶輸入字符串,也不會向指針指向的內存中寫入數據;
  • printf() 不會讀取指針指向的內容,只是簡單地給出提示,讓程序員意識到使用了一個空指針。


我們在自己定義的函數中也可以進行類似的判斷,例如:

1 void func(char *p){
2     if(p == NULL){
3         printf("(null)\n");
4     }else{
5         printf("%s\n", p);
6     }
7 }

這樣能夠從很大程度上增加程序的健壯性,防止對空指針進行無意義的操作。

其實,NULL 是在stdio.h中定義的一個宏,它的具體內容為:

#define NULL ((void *)0)

(void *)0表示把數值 0 強制轉換為void *類型,最外層的( )把宏定義的內容括起來,防止發生歧義。從整體上來看,NULL 指向了地址為 0 的內存,而不是前面說的不指向任何數據。

在進程的虛擬地址空間中,最低地址處有一段內存區域被稱為保留區,這個區域不存儲有效數據,也不能被用戶程序訪問,將 NULL 指向這塊區域很容易檢測到違規指針。

關於虛擬地址空間的概念以及程序的內存分布,我們將在《C語言內存精講》專題中深入講解,現在讀者只需要記住,在大多數操作系統中,極小的地址通常不保存數據,也不允許程序訪問,NULL 可以指向這段地址區間中的任何一個地址。

注意,C語言沒有規定 NULL 的指向,只是大部分標准庫約定成俗地將 NULL 指向 0,所以不要將 NULL 和 0 等同起來,例如下面的寫法是不專業的:

int *p = 0;

而應該堅持寫為:

int *p = NULL;

注意 NULL 和 NUL 的區別:NULL 表示空指針,是一個宏定義,可以在代碼中直接使用。而 NUL 表示字符串的結束標志 '\0',它是ASCII碼表中的第 0 個字符。NUL 沒有在C語言中定義,僅僅是對 '\0' 的稱呼,不能在代碼中直接使用。

void 指針

對於空指針 NULL 的宏定義內容,上面只是對((void *)0)作了粗略的介紹,這里重點說一下void *的含義。void 用在函數定義中可以表示函數沒有返回值或者沒有形式參數,用在這里表示指針指向的數據的類型是未知的。

也就是說,void *表示一個有效指針,它確實指向實實在在的數據,只是數據的類型尚未確定,在后續使用過程中一般要進行強制類型轉換。

C語言動態內存分配函數 malloc() 的返回值就是void *類型,在使用時要進行強制類型轉換,請看下面的例子:

1 #include <stdio.h>
2 int main(){
3     //分配可以保存30個字符的內存,並把返回的指針轉換為 char *
4     char *str = (char *)malloc(sizeof(char) * 30);
5     gets(str);
6     printf("%s\n", str);
7     return 0;
8 }

運行結果:
c.biancheng.net↙
c.biancheng.net

關於動態內存分配的概念以及 malloc() 的具體用法,我們將在《C語言內存精講》專題中詳細說明,這里重點是讓大家理解void *,它不是空指針的意思,而是實實在在的指針,只是指針指向的內存中不知道保存的是什么類型的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM