讀古今文學網 > C語言解惑 > 14.3 指針的初始化 >

14.3 指針的初始化

指針初始化,就是保證指針指向一個有效的地址。這有兩個含義,一是保證指針指向一個地址;二是指針指向的地址是有效的。

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
;
}
  

由此可見,使用已有變量的地址初始化指針能保證地址總是有效的。如果使用分配動態存儲空間來初始化指針,確保地址有效的方法是增加判斷地址分配是否成功的程序段。