隐式转换:把 C 语言数组转换为指针

在某些情况下,C 语言中的数组会被隐式转换为指针类型,使得程序员对数组的操作更加灵活。在函数参数传递中,隐式转换总是在发生。比如:

1
2
3
4
5
6
7
8
void foo1(int *b) { printf("%lu\n", sizeof(b)); }
void foo2(int c[]) { printf("%lu\n", sizeof(c)); }

int main() {
int a[5]; // 5 x 4 = 20 Bytes
foo1(a); // -> 8 Bytes
foo2(a); // -> 8 Bytes
}

在 main 函数中,a 是一个数组。我们可能会认为在 foo2 中 b 的类型是整型数组类型(输出为 20),而实际上,无论是在 foo1 还是 foo2 中,b 的类型均为指向整型的指针类型,即输出的结果均为指针类型的大小(8 个字节)。

实参:Actual Parameter
形参:Formal Parameter

参数传递的隐式转换规则:

  • 若实参为一维数组,则形参是指向数组所存元素类型的指针;
  • 若实参为多维数组,则形参是指向一维或多维数组的指针;
  • 转换并不是递归的,数组的数组会被改成为 数组的指针,而不是 指针の指针;而三维数组 char ho[2][3][4] 会被转换为 char (*)[3][4] 类型。

为了进一步说明参数传递时发生的隐式转换,我们先了解一下以下几个类型以及它们之间的关系。

指针数组:char *c[10]
指针の指针:char **c

  • 指针数组 本身是一个一维数组,存的是字符指针。

  • 指针の指针 本身是一个指针,指向的还是指针。注意它和 二维数组 的区别,可以说两者是毫无关系的

    1
    2
    char abc[3][4];
    char **pp = abc; // 会给出警告,类型不兼容。

二维数组:char c[8][10]
数组指针(行指针):char (*c)[10]

  • 二维数组本身是一个数组,存的还是数组。

  • 数组指针(行指针) 本身是一个指针,指向的是数组,一般和 二维数组 一起使用。我们要注意它和 指针数组 在写法上的区别(多了括号,先做 * 单目运算)。

接着,我们看一下隐式转换如何被运用到上述类型中。

实参与所匹配的形参(来源见参考)

图中,数组的数组(即二维数组)被隐式转换为 char (*)[10],称为 数组指针行指针。指针数组 char *c[10] 被隐式转换为 char **c,称为 指针の指针。若实参本身是指针,则无需进行隐式转换。

最后,举两个例子来验证一下参数传递中是否发生了隐式转换。

  • 例子 1

    main 函数的 argv 参数被隐式转换为 char ** 指针类型。

    1
    2
    3
    4
    // A
    int main(int argc, char *argv[]) {
    printf("%lu\n", sizeof(argv));
    }
    1
    2
    3
    4
    // B
    int main(int argc, char **argv) {
    printf("%lu\n", sizeof(argv));
    }

    程序 A 会出现 warning:

    1
    warning: sizeof on array function parameter will return size of 'char *' instead of 'char []' [-Wsizeof-array-argument]

    两个程序输出都为 8,即指针类型的大小,而不是 argv 数组的大小,因为 argv 已经被转换为一个指针了。

  • 例子 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void foo(int (*fnum)[5]) {
    printf("foo: %lu\n", sizeof(fnum));
    printf("foo: %lu\n", sizeof(&fnum));
    fnum++;
    }

    int main() {
    int num[3][5];
    printf("main: %lu\n", sizeof(num));
    printf("main: %lu\n", sizeof(&num));
    foo(num);
    return 0;
    }

    输出:

    1
    2
    3
    4
    main: 60
    main: 8
    foo: 8
    foo: 8

    可以看到,在 foo 函数里,sizeof(fnum) 的大小是 8,而不等于 main 函数里的 60。因此,fnum 不再是数组,而是一个指针。如果将 int (*fnum)[5] 改成 int fnum[][5],fnum 仍然是一个指针。

    注意:如果将 int (*fnum)[5] 改成 int (*fnum)[],不会有警告和报错。如果此时在添加指针运算 fnum++,则编译器会报错,因为编译器不知道指针运算中偏移量是多少。

    1
    error: arithmetic on a pointer to an incomplete type 'int []'

    此外,将 int fnum[][5] 改成 int fnum[][] 也会有报错。

    1
    error: array has incomplete element type 'int []'

总结:C is a nightmare.

参考: