指針初始化,就是保證指針指向一個有效的地址。這有兩個含義,一是保證指針指向一個地址;二是指針指向的地址是有效的。
1.數值表示地址
為了更深入地理解這一點,首先要記住指針是與地址相關的。指針變量的取值是地址值,但如何用數值來代表地址值呢?請看下面的例子。
【例14.4】演示表示地址值的例子。
#include <stdio.h> void main () { int a=25 ,b=55 ; printf (\"%d ,%dn\" ,&a ,&b ); printf (\"%d ,%dn\" ,*&a ,*&b ); printf (\"%d ,%dn\" ,* (int * )1245052 ,* (int * )1245048 ); }
程序輸出結果如下。
1245052 ,1245048 25 ,55 25 ,55
「&a」表示變量a的地址,「*&a」表示存入地址裡的值,也就是變量a的值。既然&a代表的是存儲變量a的十進制地址1245052,是否可以使用「*1245052」呢?
答案是否定的,編譯器無法解釋「*1245052」。要想讓「1245052」表示地址,必須顯式說明,即讓它與指針關聯起來,使用「int*」將這個十進制數字強制轉換成地址。這就是通常說的「地址就是指針」的含義。
2.有效地址和無效地址
【例14.5】演示有效地址和地址值的例子。
#include <stdio.h> void main () { int a=25 ,b=55 ,*p ; //4 printf (\"%d ,%dn\" ,&a ,&b ); //5 a=1245048 ; //6 p= (int * )a ; //7 printf (\"%d ,%d ,%d ,%dn\" ,&a ,a ,* (int * )a ,*p ); //8 *p=1245048 ; //9 printf (\"%d ,%dn\" ,p ,*p ); //10 }
第6行只是將與變量b的地址值相等的數值賦給變量a,所以變量a的值是十進制數值,不是地址。第7行則是將變量a的數值強制轉換成十進制地址值賦給指針p,即指針指向變量b的地址。這表現在第8行的輸出「&a」仍然是變量a的地址,而「a」則是和變量b的地址值相等的數值。已知「(int*)a」代表變量b的地址,所以「*(int*)a」和「*p」都是輸出變量b的值。由此得這一行的輸出為:
1245052 ,1245048 ,55 ,55
第9行給*p賦值,*p要求的是數值,這裡是把與變量b地址值相等的數值賦給*p,p是指向變量b的地址,所以輸出
1245048 ,1245048
是相同的。但要注意一個是地址的值,一個是數據的數值。由此可知,一個地址值,必須是指針類型的數據(這裡是int*)。因為地址值是指針類型的數值,所以才有「地址就是指針」的說法。指針要用一個地址值初始化,而變量的地址肯定是有效的,所以用變量地址初始化指針是萬無一失的。不過,有時並沒有變量可用,這就要自己為它分配正確的地址。
這個例子不僅演示如何使用地址值,還演示有效地址的概念。這裡是在確定變量地址之後,才使用它們,所以是有效的。一般認為,所謂地址有效是指在計算機能夠有效存取的範圍內。由此可見,這個有效並不能保證程序正確。指針超出本程序使用的地址範圍,可能帶來不可估計的錯誤。
為了保證賦予的地址有效,避免像上面例子那樣直接使用強制轉換,而是直接使用變量地址賦值。例如:
int a=25 ,b=55 ,*p=&b ;
或者使用語句
int a=25 ,b=55 ,*p ; p=&b ;
由此可見,對於一個指針,需要賦給它一個地址值。上面的例子在賦給指針地址時,不是隨意的,而是經過挑選的。如果隨便選一個地址,可能是計算機不能使用的地址,也就是無效的地址。
【例14.6】演示無效地址的例子。
#include <stdio.h> void main () { int a=25 ,b=55 ,*p ; //4 printf (\"%d ,%dn\" ,&a ,&b ); //5 a=12345 ; //6 p= (int * )a ; //7 printf (\"%d ,%d ,%dn\" ,&a ,a ,p ); //8 printf (\"%dn\" ,*p ); //9 }
如果註釋掉第9行,運行結果如下:
1245052 ,1245048 1245052 ,12345 ,12345
從運行結果知道,把十進制地址12345賦給了指針p,程序可以將p指向的地址打印出來。但這是個無效的地址,所以不能使用「*p」。這時雖然編譯沒問題,但卻產生運行時錯誤。
由此可見,使用指針的危險性就是賦予它一個無效地址。如果有效地避免了這一點,就可以運用自如。
3.無效指針和NULL指針
編譯器保證由0轉換而來的指針不等於任何有效的指針。常數0經常用符號NULL代替,即定義如下:
#define NULL 0
當將0賦值給一個指針變量時,絕對不能使用該指針所指向的內存中存儲的內容。NULL指針並不指向任何對象,但可以用於賦值或比較運算。除此之外,任何因其他目的而使用NULL指針都是非法的。因為不同編譯器對NULL指針的處理方式也不相同,所以要特別留神,以免造成不可收拾的後果。
如上所說,把指針初始化為NULL,就是用0號地址初始化指針,而且這個地址不允許程序操作,但可以為編程提供判別條件。尤其是申請內存時,假如沒有分配到合適的地址,系統將返回NULL。
C編譯程序都提供了內存分配函數,最主要的是malloc和calloc,它們是標準C語言函數庫的一部分,都是為要寫的數據在內存中分配一個安全區。一旦找到一個大小合適的內存空間,就分配給它們,並將這部分內存的地址作為一個指針返回。malloc和calloc的主要區別是:calloc清除所分配的內存中的所有字節,即置零所有字節;malloc僅分配一塊內存,但所有字節的內容仍然是被分配時所含的隨機值。
malloc和calloc所分配的內存空間,都可以用free函數釋放出來。這3個函數的原型在文件stdlib.h中,但很多編譯器又放在頭文件malloc.h中,注意查閱手冊。
在C目前所提供的最新編譯程序中,malloc和calloc都返回一個void型的指針,也就是說,返回的地址值可以假設為任何合法的數據類型的指針。這個強制轉換可以在聲明中進行,如把它們聲明為字符型、整型、長整型、雙精度或其他任何類型。
【例14.7】找出程序中的錯誤並改正。
#include <stdlib.h> #include <stdio.h> void main () { char *p ; *p = malloc (100 ); gets (p ); printf (p ); free (p ); }
malloc所返回的地址並未賦給指針p,而是賦給了指針p所指的內存位置。這一位置在此情況下也是完全未知的。下面語句
char *p ; *p = ( char * ) malloc (100 );
只是將void指針強制轉化為char類型的指針,所以它與上面的等效,都是錯誤的。如果改為
char *p ; p = malloc (100 );
的方式,則是可以的,但它也有另外一個更為隱蔽的錯誤。如果內存已經用完了,malloc將返回空(NULL)值,這在C語言中是一個無效的指針。正確的程序應該把對指針的有效性檢查加入其中,並及時釋放不用的動態內存空間。下面是一個正確而完整的實例。
#include <stdlib.h> #include <stdio.h> void main ( ) { char *p ; p = (char * ) malloc (100 ); if (p==NULL ) { printf (\" 內存分配錯誤!n\" ); exit (1 ); } gets (p ); printf (p ); free (p ); }
在設計C程序時,對指針的初始化有兩種方法:使用已有變量的地址或者為它分配動態存儲空間。好的設計方法是盡可能早點為指針賦值,以免遺忘造成使用沒有初始化的指針。
對於上述程序,如果設置
p =NULL ;
則程序執行if語句「{}」裡的部分,輸出「內存分配錯誤!」,然後退出程序。在程序設計中,有時正好利用NULL作為判別條件。下面就是一個典型的例子。
【例14.8】完善下面的程序。
#include <stdio.h> char s1[16] ; char *mycopy (char *dest ,char *src ) { while (*dest++=*src++ ); return dest ; } void main ( ) { char s2[16]=\"how are you ?\" ; mycopy (s1 ,s2 ); printf (s1 ); printf (\"n\" ); }
這個程序編譯沒有錯誤,但不夠完善。這主要是mycopy函數中沒有採取預防指針為NULL(又稱0指針)的情況。解決的方法很多,下面是簡單處理的例子。
char *mycopy (char *dest ,char *src ) { if (dest == NULL || src == NULL ) return dest ; while (*dest++=*src++ ); return dest ; }
由此可見,使用已有變量的地址初始化指針能保證地址總是有效的。如果使用分配動態存儲空間來初始化指針,確保地址有效的方法是增加判斷地址分配是否成功的程序段。