Array Name 其實就是 Pointer

寫這篇文章的原因在於自己常常混淆一個觀念,在 C 語言中,若要傳遞陣列到函數中,函數的宣告可以寫為以下兩種格式:

int func (int *arr);

int func (int a[]);

如果要傳遞陣列到函數,以下方式都是正確的:

int data[10];

func (data);
func (&data[0]);

一直以來都沒有理解這兩種用法,導致使用或看 code 時,有不小的困擾,想藉著這篇文章探討這兩個用法的本質。

先說結論,其實 array 名稱在兩種狀況下會被自動轉為「一個 pointer 指向 array 的第一個元素」

  1. 作為 function parameter 時
    func (int arr[]) 等於 func (int *a)
  2. 在 expression 內使用時

    int arr[] = {1, 3, 5, 7, 9};
    // arr 為 pointer ,這邊並不是使用 &arr
    int *ptr = arr;

規格書怎麼說

在 C99 的規格書 6.5.2.1 Array subscripting 章節中,有以下敘述:

One of the expressions shall have type ‘‘pointer to object type’’, the other expression shall have integer type, and the result has type ‘‘type’

T E1[E2] 來解釋這段敘述,一般都會以此形式宣告 array ,其中 T 代表 array 內元素的型別,其中 E1 的型別即為 pointer to object type ,而 E2 的型別為 integer 。

舉一個具體的例子會更好理解, int arr[5] 代表宣告一個含有五個整數型別元素的陣列,在方括號內的一定會是整數型別,有趣的是 arr 的型別,arr 為指向整數物件的指標。

陣列名稱本身可以當作指標來運用,指標的特性進而衍生出更多陣列的操作,以下逐一探討。

Pointer 的操作

取得陣列內元素,通常以 subscript 運算子 ( [ ] ) 獲得, arr[1] 即得到陣列中 index 為 1 的元素,想要得到元素的 address ,再加上 address-of 運算子即可:

int arr [5] = {1, 3, 5, 7, 9};
int *ptr_foo = &arr[1];

但基於陣列名稱就是一個指標的事實下,有了以下方式來得到陣列中其他元素的 address :

int *ptr_bar = arr + 1; // ptr_bar 指向 arr[1],等價於 ptr_foo

Deference 陣列名稱

竟然陣列名稱等於一個 pointer ,代表可以透過 * (提領運算子)來提領(deference)其值。

回到 C99 規格書中,其中有提到

The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))).

以具體例子來看, arr[2] 等價於 ( * ( (arr) + (2) ) ) ,這邊另外提一點, dereference 運算子的優先級比 addition 運算子高,看以下例子:

int foo = *arr + 3;    // 等價於 arr[0] + 3
int bar = * (arr + 3); // 等價於 arr[3]

對 pointer 取下標

前面不斷強調,陣列名稱等價於「指向陣列第一個元素的 pointer 」。

平常取得陣列元素時,對陣列名稱使用下標運算子即可取得,例如 arr[1] ,但這樣的用法,不就等於是對 pointer 使用下標運算子,思考以下例子:

int var1 = arr[3];
int var2 = * (arr + 3);  // 前面有提到等價於 arr[3]

變數 var1var2 是等價的,前者對「指向陣列第一個元素的 pointer」取下標,後者先移動 「指向陣列第一個元素的 pointer」再提取其值 (deference),兩個變數的結果是一樣,也隱含著其背後的操作都是一樣,至此,可以做出一個結論,對 pointer 取下標就如同「移動此 pointer 並 deference 其值」。

再看以下例子,更能體會對陣列名稱取下標的美妙之處同時檢驗自己是否理解這篇文章的內容:

int *ptr = &arr[3];
int fourth_var = ptr[1]; // 向前移動指標,並提領其值
int first_var = ptr[-2]; // 向後移動指標,並提領其值,first_var 等價於 arr[1]

Referenece

  1. 你所不知道的C語言:指標篇
  2. C99 規格書
  3. C++ Primer Fourth Edition Ch4 導入指標