誤解陣列衰變

使用多維陣列,指標陣列等的程式碼中的一個常見問題是 Type**Type[M][N] 基本上是不同的型別:

#include <stdio.h>

void print_strings(char **strings, size_t n)
{
    size_t i;
    for (i = 0; i < n; i++)
        puts(strings[i]);
}

int main(void)
{
    char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
    print_strings(s, 4);
    return 0;
}

示例編譯器輸出:

file1.c: In function 'main':
file1.c:13:23: error: passing argument 1 of 'print_strings' from incompatible pointer type [-Wincompatible-pointer-types]
         print_strings(strings, 4);
                       ^
file1.c:3:10: note: expected 'char **' but argument is of type 'char (*)[20]'
     void print_strings(char **strings, size_t n)

該錯誤表明 main 函式中的 s 陣列被傳遞給函式 print_strings,函式 print_strings 需要不同的指標型別。它還包括一個註釋,表示 print_strings 所期望的型別以及從 main 傳遞給它的型別。

問題是由於稱為陣列衰減的東西。當 s 的型別 char[4][20](由 4 個 20 個字元的陣列組成的陣列)傳遞給函式時會發生什麼呢?它變成了指向它的第一個元素的指標,好像你寫了 &s[0],它的型別為 char (*)[20](指向 1 個陣列的指標) 20 個字元)。對於任何陣列都會發生這種情況,包括指標陣列,陣列陣列(3-D 陣列)陣列以及指向陣列的指標陣列。下面的表格說明了陣列衰減時會發生什麼。突出顯示型別描述中的更改以說明發生的情況:

在衰變之前 衰變之後
char [20] 陣列(20 個字元) char * 指標(1 個字元)
char [4][20] (4 個 20 個字元陣列 )的陣列 char (*)[20] 指向(1 個 20 個字元的陣列
char *[4] 陣列(4 個指向 1 個字元) char ** 指向(1 指向 1 個字元的指標
char [3][4][20] 陣列(3 個陣列,4 個陣列,20 個字元) char (*)[4][20] 指向(1 個 4 個 20 個字元陣列的陣列)
char (*[4])[20] 陣列(4 個指向 1 個 20 個字元的陣列) char (**)[20] 指向(1 個指向 1 個 20 個字元陣列的指標

如果陣列可以衰減到指標,那麼可以說指標可以被認為是至少 1 個元素的陣列。一個例外是空指標,它指向什麼都不是,因此不是陣列。

陣列衰減只發生一次。如果陣列已衰減為指標,則它現在是指標,而不是陣列。即使你有一個指向陣列的指標,記住指標可能被認為是至少一個元素的陣列,因此已經發生了陣列衰減。

換句話說,指向陣列的指標(char (*)[20])永遠不會成為指向指標(char **)的指標。要修復 print_strings 功能,只需讓它接收正確的型別:

void print_strings(char (*strings)[20], size_t n)
/* OR */
void print_strings(char strings[][20], size_t n)

當你希望 print_strings 函式對任何字元陣列都是通用的時候會出現問題:如果有 30 個字元而不是 20 個字元怎麼辦?還是 50?答案是在陣列引數之前新增另一個引數:

#include <stdio.h>

/*
 * Note the rearranged parameters and the change in the parameter name
 * from the previous definitions:
 *      n (number of strings)
 *   => scount (string count)
 *
 * Of course, you could also use one of the following highly recommended forms
 * for the `strings` parameter instead:
 *
 *    char strings[scount][ccount]
 *    char strings[][ccount]
 */
void print_strings(size_t scount, size_t ccount, char (*strings)[ccount])
{
    size_t i;
    for (i = 0; i < scount; i++)
        puts(strings[i]);
}

int main(void)
{
    char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
    print_strings(4, 20, s);
    return 0;
}

編譯它不會產生任何錯誤並導致預期的輸出:

Example 1
Example 2
Example 3
Example 4