为什么在C语言中,function(char * array[])是一个有效的函数定义,而(char (*array)[]不是?

7

我认为这是因为前者是指向char类型指针的数组,而后者是指向char类型数组的指针,我们需要正确指定被指向的对象的大小以便进行函数定义。在前者中;

function(char * p_array[])

指针所指向的对象的大小已经包含在内(它是一个指向字符的指针),但后者...
function(char (*p_array)[])

需要将p_array指向数组的大小作为p_array定义的一部分吗?我已经思考了很长时间,现在自己已经搞糊涂了,请有人告诉我我的推理是否正确。


这是关于C还是C++的问题?标题说“C”,但标签说“两者都可以”,答案取决于您使用哪个... - Dietrich Epp
@Dietrich Epp - C和C ++是不同的,但这个问题的答案并不取决于你使用哪一个。 - Chris Lutz
@Chris Lutz:这两种写法在C语言中都是完全有效的,但其中一种在C++中是无效的。 - Dietrich Epp
抱歉,标签已编辑。这是C语言,我错误地假设两者都适用于两种语言。 - Matt
@Dietrich Epp - 我以为指向数组的指针需要一个数组大小,但是cdecl很奇怪,我不想查了。 - Chris Lutz
这个问题在当前的形式下毫无意义。在C语言中,两个参数声明都是完全有效的。 - AnT stands with Russia
4个回答

16

在C语言中,两者都是有效的,但在C++中不是。你通常是正确的:

char *x[]; // array of pointers to char
char (*y)[]; // pointer to array of char

然而,如果数组出现在函数参数中,则它们会退化为指针。因此它们变成了:

char **x; // Changes to pointer to array of pointer to char
char (*y)[]; // No decay, since it's NOT an array, it's a pointer to an array
在 C 语言的数组类型中,允许其中一个大小未确定。此未确定的大小必须是最左边的(哎呀,我一开始说成是最右边了)。因此,
int valid_array[][5]; // Ok
int invalid_array[5][]; // Wrong

你可以将它们链接起来使用,但我们很少有理由这样做。

int (*convoluted_array[][5])[][10];

注意:如果一个数组类型中包含[],则该类型是不完整的。您可以传递指向不完整类型的指针,但某些操作将无法工作,因为它们需要一个完整的类型。例如,以下操作将无法正常工作:

void func(int (*x)[])
{
    x[2][5] = 900; // Error
}
这是一个错误,因为编译器需要知道x[0]x[1]有多大才能找到x[2]的地址。但是x[0]x[1]的类型是int[]——这是一种不完整的类型,没有关于其大小的信息。如果您想象一下类型的“未衰减”版本,即int x[][],就会更清楚,它显然是无效的C语言代码。如果您想在C中传递二维数组,有几个选项:
  • 传递一个带有大小参数的一维数组。

  • void func(int n, int x[])
    {
        x[2*n + 5] = 900;
    }
    
    使用指向行的指针数组。如果你有真正的二维数据,这种方法可能有些笨拙。
    void func(int *x[])
    {
        x[2][5] = 900;
    }
    
  • 使用固定大小。

    void func(int x[][5])
    {
        x[2][5] = 900;
    }
    
  • 使用变长数组(仅限C99,因此可能不适用于微软编译器)。

  • // There's some funny syntax if you want 'x' before 'width'
    void func(int n, int x[][n])
    {
        x[2][5] = 900;
    }
    
    这是即使对于C语言老手来说,也经常出现问题的领域。许多语言缺乏内在的“开箱即用”支持真正的,可变大小的多维数组(如C ++、Java和Python),尽管少数语言具有此功能(如Common Lisp、Haskell和Fortran)。您会看到很多代码使用数组的数组或手动计算数组偏移量。

当我尝试将一个多维数组作为实际参数传递给一个带有形式参数(char (*y)[])的函数时,我会收到错误信息:无效使用未指定边界的数组。这是否意味着虽然定义是有效的,但实际上对任何事情都没有用处? - Matt
char (*p_array)[]是指向不完整类型的指针,因此您不能对其进行解引用。 - caf
@Matt:错误不在函数声明中,而在函数体中(第18行)。请查看编辑。 - Dietrich Epp
@caf:从技术上讲,不完整类型可以被解引用,但是您不能将整数添加到指向不完整类型的指针中。因此,(*p_array)[j]是有效的,但p_array[i][j]则无效。 - Dietrich Epp
啊,那个修改过的解释有很多道理。感谢您的时间和帮助! - Matt
显示剩余2条评论

3

注意:
以下答案是从C++的角度回答的,当标记为C++时添加了该答案。如果标记更改为仅C,则两个提到的示例在C中都是有效的。

是的,你的推理是正确的。
如果您尝试编译编译器给出的错误,则会出现:

parameter ‘p_array’ includes pointer to array of unknown bound ‘char []’

在C++中,数组大小需要在编译时确定。C++标准不允许使用可变长度数组(VLA)。一些编译器支持这个特性作为扩展,但这不符合标准规范。

3

这两个声明是非常不同的。在函数参数声明中,对参数名称直接应用[]的声明符与*完全等价,因此你的第一个声明在所有方面都与以下声明完全相同:

function(char **p_array);

然而,这并不适用于参数类型的递归。你的第二个参数的类型是char (*)[],它是一个指向未知大小数组的指针 - 它是一个不完整类型的指针。你可以轻松地使用这种类型声明变量 - 以下是有效的变量声明:

char (*p_array)[];

就像指向其他不完整类型的指针一样,您不能对此变量(或函数参数)执行任何指针算术运算 - 这就是您出错的地方。请注意,[]运算符被指定为a[i]*(a+i)相同,因此该运算符不能应用于您的指针。当然,您可以将其作为指针使用,因此以下内容是有效的:

void function(char (*p_array)[])
{
    printf("p_array = %p\n", (void *)p_array);
}

这种类型也兼容指向任何其他固定大小的char数组的指针,因此您也可以这样做:

void function(char (*p_array)[])
{
    char (*p_a_10)[10] = p_array;

    puts(*p_a_10);
}

...甚至包括这个:

void function(char (*p_array)[])
{
    puts(*p_array);
}

尽管这样做几乎没有什么意义:你可以直接声明类型为char *的参数。
请注意,尽管允许使用*p_array,但不允许使用p_array[0]

1
所以基本上,函数定义(char (p_array)[])是允许的,函数可以使用p_array指针并对其进行解引用以获取值,但不能对p_array指针本身进行算术运算。因此,我们不能在p_array指针上执行算术运算(如(p_array+1)),因为它不知道+1应该添加到p_array多少,因为它不知道它指向什么类型。但是像*((p_array)+1)这样的算术运算应该是有效的,因为它知道p_array是指向char类型的指针,因此知道应该添加多少+1。 - Matt
1
@Matt:是的。第二个例子等同于(*p_array)[1],这是可以的。 - caf

2

因为,

(1) function(char * p_array[])

等同于char **p_array,即有效的双指针。

(2) function(char (*p_array)[])

您说得对,p_array是指向char数组的指针。但是,当它作为函数参数出现时,它需要具有固定的大小。您需要提供大小,这样它也将变得有效。


@Mahesh,不是的,第二个是字符数组的指针(但需要有固定的大小)。另外,字符数组是一种数据类型;我们不能有一个指向它的函数指针。你可以这样赋值:char (*p)[3] = &a; 其中 char a[3]; - iammilind
1
@iammilind:问题标题说的是“C”,但你正在编译为C++。 - Dietrich Epp
1
@Dietrich Epp: 该Q标记了C和C++,但标题只涉及到C。在C和C++中,这些行为将会有所不同。C允许VLA而C++则不允许。 - Alok Save
2
@Chris:抱歉,我经常会犯错,所以使用编译器-pendantic -ansi -Wall -Wextra进行双重检查,因为找到适当的部分需要更多时间。(适当的部分是n1570第6.7.6.2和6.7.6.3节,尽管您基本上必须阅读这两个部分才能弄清楚这是可以的。它们不是VLA,而是不完整类型的数组。 VLA是C99中的新功能,但不完整类型的数组与C本身一样古老。) - Dietrich Epp
@Dietrich - 那很有道理。我现在认为你是对的,尽管我认为这段代码有效令人遗憾。 - Chris Lutz
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接