指標與位址


在〈變數〉曾經說過,變數提供具名稱的記憶體儲存空間,一個變數關聯一個資料型態、儲存的值與儲存空間的位址值。

如果想知道變數的記憶體位址,可以使用 & 取址運算子(Address-of operator),例如:

#include <stdio.h>

int main(void) {
    int n = 10;

    printf("n 的值:%d\n", n);
    printf("n 的位址:%p\n", &n);

    return 0;
}

執行結果:

n 的值:10
n 的位址:0061FECC

這個程式中,宣告了一個 int 整數變數 nn 儲存的記憶體位址是 0061FECC,這是 16 進位表示法,如果 int 長度是 4 個位元組,從 0061FECC 後的 4 個位元組是 n 配置到的記憶體空間,現在這個空間中儲存值為 10。

直接存取變數會對分配到的空間作存取,指標(Pointer)是一種變數,儲存記憶體位址,要宣告指標,是使用以下的語法:

type *ptr;

ptr 可儲存位址,而 type 為該位址儲存值的型態,實際宣告的方式如下:

int *n;
float *s;
char *c;

雖然宣告指標時,C 習慣將 * 前置在變數名稱前,不過 n 的型態是 int*s 的型態是 float*,而 c 的型態是 char*,指標的型態決定了位址上的資料如何解釋,以及如何進行指標運算(Pointer arithmetic)。

可以使用 & 運算子取出變數的位址值並指定給指標,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    int *p = &n ;

    printf("n 的位址:%p\n", &n);
    printf("p 儲存的位址:%p\n", p);

    return 0;
}

執行結果:

n 的位址:0061FEC8      
p 儲存的位址:0061FEC8  

以上的程式使用 & 來取得變數 n 的位址,然後指定給指標 p,因此 p 儲存的位址就與 &n 取得的位址相同。

可以使用提取 (Dereference)運算子 * 來提取指標儲存的位址中之資料,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    int *p = &n;

    printf("指標 p 儲存的值:%p\n", p);
    printf("取出 p 儲存位址處之值:%d\n", *p);

    return 0;
}

執行結果:

指標 p 儲存的值:0061FEC8
取出 p 儲存位址處之值:10

如果已經取得了記憶體位址,將某值指定給 *P 時,該記憶體位址的值也會改變,這相當於告訴程式,將值放到 P 儲存的位址處,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    int *p = &n ;

    printf("n = %d\n", n);
    printf("*p = %d\n", *p);

    *p = 20;

    printf("n = %d\n", n);
    printf("*p = %d\n", *p);

    return 0;
}

執行結果:

n = 10
*p = 10
n = 20
*p = 20

當指標 p 儲存的位址與變數 n 的位址相同時,對 *p 進行指定,就會將值直接存入該記憶體位置,因此透過變數 n 取出的值也就改變了。

如果宣告指標但不指定初值,則指標儲存的位址是未知的,存取未知位址的記憶體內容是危險的,例如:

int *p; 
*p = 10;

這個程式片段並未初始指標就指定值給 *p,會造成不可預知的結果,最好為指標設定初值,如果指標一開始不儲存任何位址,可設定初值為 0,例如:

int *p = 0;

在指標宣告時,可以靠在名稱旁邊,也可以靠在關鍵字旁邊,或者是置中,例如:

int *p1;
int* p2;
int * p3;

這三個宣告方式都是可允許的,C 開發者比較傾向用第一個,因為可以避免以下的錯誤:

int* p1, p2;

這樣的宣告方式,初學者可能以為 p2 也是指標,但事實上並不是,以下的宣告 p1p2 才都是指標:

int *p1, *p2;

有時候,只希望儲存記憶體的位址,可以使用 void* 來宣告指標,例如:

void *p;

void* 型態的指標沒有任何型態資訊,只用來儲存位址,不可以使用 * 運算子對 void* 型態指標提取值,而必須轉型至對應的型態,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    void *p = &n ;

    // 下面這句不可行,void 型態指標不可取值 
    // printf("%d\n", *p);

    // 轉型為int型態指標並指定給iptr 
    int *iptr = (int*) p;
    printf("%d\n", *iptr);

    return 0;
}

執行結果:

10

順便來看一下 const 宣告的變數,被 const 宣告的變數一但被指定值,就不能再改變變數的值,雖然可以強制如下改變變數值的:

const int n = 10;
int *p = &n; //  warning: initialization discards 'const' qualifier from pointer target type
*p = 20;
printf("%d\n", n);

然而,gcc 會產生警訊,執行程式會顯示 20,如果不想該位址的值被改變,可以用 const 宣告指標,例如:

const int n = 10;
const int *p = &n;
*p = 20; // error, assignment of read-only location 

必須留意的是,上面的 p 並不是唯讀,若想要有唯讀的指標,必須使用指標常數,也就是一旦指定給指標值,就不能指定新的記憶體位址值給它,例如:

int x = 10;
int y = 20;
int* const p = &x;
p = &y;  // error,  assignment of read-only variable `p'

因此,若 xyconst 宣告,對應的指標常數宣告會是如下:

const int x = 10;
const int y = 20;
const int* const p = &x;
p = &y;  // error,  assignment of read-only variable `p'