误解阵列衰变

使用多维数组,指针数组等的代码中的一个常见问题是 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