指向未指定大小数组“(*p)[]”的指针在 C++ 中是非法的,但在 C 中是合法的

Posted

技术标签:

【中文标题】指向未指定大小数组“(*p)[]”的指针在 C++ 中是非法的,但在 C 中是合法的【英文标题】:Pointer to array of unspecified size "(*p)[]" illegal in C++ but legal in C 【发布时间】:2015-03-09 23:06:42 【问题描述】:

我刚刚发现这在 C++ 中是非法的(但在 C 中是合法的):

#include <stdio.h>
#include <stdlib.h>
#define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))

int accumulate(int n, const int (*array)[])

    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) 
        sum += (*array)[i];
    
    return sum;


int main(void)

    int a[] = 3, 4, 2, 4, 6, 1, -40, 23, 35;
    printf("%d\n", accumulate(ARRAY_LENGTH(a), &a));
    return 0;

使用gcc -std=c89 -pedantic 编译没有问题,但使用g++ 编译失败。当我尝试使用 g++ 编译它时,我收到以下错误消息:

main.cpp:5:37: error: parameter 'array' includes pointer to array of unknown bound 'int []'
 int accumulate(int n, int (*array)[])
                                     ^
main.cpp: In function 'int main()':
main.cpp:18:50: error: cannot convert 'int (*)[9]' to 'int (*)[]' for argument '2' to 'int accumulate(int, int (*)[])'
     printf("%d\n", accumulate(ARRAY_LENGTH(a), &a));

我已经在我的 C 代码中使用它很长时间了,但我不知道它在 C++ 中是非法的。对我来说,这似乎是一种有用的方法来记录一个函数采用一个事先不知道大小的数组。

我想知道为什么这是合法的 C 而无效的 C++。我还想知道是什么让 C++ 委员会决定取消它(并破坏与 C 的兼容性)。

那么为什么这个 C 代码是合法的,而 C++ 代码却是非法的呢?

【问题讨论】:

C++ 分离时存在的 C 版本是否有未指定大小的数组?我认为在那些日子里你必须将它们声明为指针,并且能够使用 [] 是后来添加的。 C++ 是从 C89 中分离出来的,并且该示例使用 gcc -std=c89 -pedantic 编译没有问题,所以我不认为这是后来添加的。 请注意,如果您将 n 转换为模板参数 (template&lt;int n&gt;) 并在数组类型 (int (*array)[n]) 中使用它,您的代码应该可以工作。另请注意,甚至可以(并且大多数时候更容易)使用对数组的引用而不是指向数组的指针:int (&amp;array)[n]。然后用accumulate(&amp;a) 调用它,让编译器为你推导出n! ;) 将“指向任意大小的数组的指针”指定为函数参数的常规方法是accumulate(int n, int array[]),这在 C 和 C++ 中都是合法的(并且具有预期的效果) @ChrisDodd:英语含糊不清,太糟糕了。这就是我的意思:“我同意你的观点,除了一件事:int array[] 具有误导性。为了提高清晰度,你应该把它写成accumulate(int n, int* array)”这里数组没有被修改,这就是为什么我也建议@ 987654337@. 【参考方案1】:

Dan Saks wrote about this in 1995,在 C++ 标准化之前:

委员会决定,像这样的职能,接受 指向未知边界的数组的指针或引用,复杂 C++ 中的声明匹配和重载解析规则。这 委员会一致认为,由于这些职能几乎没有用处,而且 相当罕见,最简单的方法是禁止它们。因此, C++ 草案现在声明:

如果一个参数的类型包括一个类型的表单指针 T 的未知边界数组或对未知边界数组的引用 T,程序格式错误。

【讨论】:

该禁令已被the resolution of CWG issue 393 删除,在上次委员会会议上通过。【参考方案2】:

C++ 没有 C 的“兼容类型”概念。在 C 中,这是对变量的完全有效的重新声明:

extern int (*a)[];
extern int (*a)[3];

在 C 中,这是对同一函数的完全有效的重新声明:

extern void f();
extern void f(int);

在 C 中,这是特定于实现的,但通常是对同一变量的有效重新声明:

enum E  A, B, C ;
extern enum E a;
extern unsigned int a;

C++ 没有这些。在 C++ 中,类型要么相同,要么不同,如果它们不同,那么它们之间的差异几乎不用担心。

同样,

int main() 
  const char array[] = "Hello";
  const char (*pointer)[] = &array;

在 C 中有效,但在 C++ 中无效:array,尽管有 [],被声明为长度为 6 的数组。pointer 被声明为指向未指定长度数组的指针,这是一个不同的类型。没有从const char (*)[6]const char (*)[] 的隐式转换。

正因为如此,在 C++ 中,使用指向未指定长度数组的指针的函数几乎毫无用处,而且几乎可以肯定是程序员的错误。如果你从一个具体的数组实例开始,你几乎总是已经有了大小,所以你不能把它的地址传递给你的函数,因为你会遇到类型不匹配。

在您的示例中也不需要指向未指定长度的数组的指针:在 C 中编写它的正常方法(恰好在 C++ 中也有效)是

int accumulate(int n, int *array)

    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) 
        sum += array[i];
    
    return sum;

被称为accumulate(ARRAY_LENGTH(a), a)

【讨论】:

有一个开放的EWG issue 允许转换。 @T.C.啊,很高兴知道。我想如果它确实被允许,它可能只会在一个方向上被允许。从char(*)[6]char(*)[] 的隐式转换是安全的,但从char(*)[]char(*)[6] 的隐式转换则不是。因为在 C 中,甚至没有任何转换(类型只是兼容),您可以编写像 int main() int array[6]; int (*ptr1)[] = &amp;array; int (*ptr2)[100] = ptr1; 这样的代码,它通常甚至不会收到任何编译器警告,更不用说错误了。

以上是关于指向未指定大小数组“(*p)[]”的指针在 C++ 中是非法的,但在 C 中是合法的的主要内容,如果未能解决你的问题,请参考以下文章

c++二维数组和二级指针

在 C++ 中使用指向我的数组的指针会给我一个“未处理的异常”吗?

C指针:指向一个固定大小的数组

在c++编程中,怎么定义对象数组的指针?

C++:指针小白易混点

二维数组名不能赋值给二级指针- -