赋值<常量数组指针> = <数组指针>: 不兼容的指针。

9
当我编译以下内容时:
double da[ 3 ] = { 2., 3., 4. };
double (* pda)[ 3 ] = &da;
double const (* cpda)[ 3 ] = pda; // gcc: warning; MSVC: ok

gcc 警告我

warning: initialization from incompatible pointer type [enabled by default]

问题是什么?从技术上来说这些是不同类型,但我在这里看不到任何危险,double const (*)[ 3 ] 对我来说甚至比 double (*)[ 3 ] 更安全。我进行了一些测试,结果让我更加困惑:
1)MSVC对于double const (* cpda)[ 3 ] = pda;的赋值非常满意,没有错误,也没有警告。
2)gcc和MSVC都很满意。
double d = 1.;
double * pd = &d;
double const * cpd = pd;  // gcc: ok; MSVC: ok

虽然它们也是不同的类型。

3)在这个例子中

double d = 1.;
double * pd = &d;
double * * ppd = &pd;
double const * * cppd = ppd;  // gcc: warning; MSVC: error

gcc发出了相同的警告,但MSVC发出了错误信息(!)。

这里谁是正确的? gcc还是MSVC?


测试结果。

编译器:

1) gcc版本4.7.2: http://www.compileonline.com/compile_c_online.php

2) MSVC (作为C++代码) 版本'VS2012CTP' 17.00.51025用于x86: http://rise4fun.com/vcpp

3) MSVC (作为C代码) VS2010:离线测试

int main()
{
    double d = 1.;

    double * pd = &d;
    double const * cpd = pd;
    // gcc: ok
    // MSVC C++: ok
    // MSVC C: ok

    double * * ppd = &pd;
    double const * * cppd = ppd;
    // gcc: warning: initialization from incompatible pointer type [enabled by default]
    // MSVC C++: error C2440: 'initializing' : cannot convert from 'double **' to 'const double **'
    // MSVC C: ok

    double da[ 3 ] = { 2., 3., 4. };

    double (* pda)[ 3 ] = &da;
    double const (* cpda)[ 3 ] = pda;
    // gcc: warning: initialization from incompatible pointer type [enabled by default]
    // MSVC C++: ok
    // MSVC C: ok

    cpd, cpda;
    return 0;
}

编辑:

我刚在Visual Studio上将这段代码编译为C代码(不是C++),没有任何错误和警告。我已经编辑了上面的注释。


5
所需阅读内容:http://c-faq.com/ansi/constmismatch.html。该文章讨论了在C语言中使用const关键字的一些问题。它解释了当一个指针指向一个非常量变量时,为什么const限定符不能被强制转换掉。该文还介绍了一些典型的错误用法以及如何正确地使用const来避免这些错误。 - n. m.
@n.m. 谢谢,我现在明白为什么这很危险了。 - kotlomoy
3个回答

7

这是标准解释的不同理解,GCC认为类型不兼容,而MSVC和Clang则认为兼容。

6.7.6.1 (2):

对于两个指针类型要兼容,它们必须具有完全相同的限定符,并且都必须是指向兼容类型的指针。

变量 pdacpda 的类型具有完全相同的限定符[没有限定符],所以问题在于它们是否指向兼容类型,即 double[3]const double[3] 是否是兼容类型?

6.7.6.2 (6):

对于两个数组类型要兼容,它们的元素类型必须是兼容的,并且如果两个大小说明符都存在且是整数常量表达式,则这两个大小说明符必须具有相同的常量值。如果这两个数组类型用于需要它们兼容的上下文中,那么如果两个大小说明符计算出的值不相等,则行为未定义。

所以问题在于 doubleconst double 是否是兼容类型。

6.7.3 (10):

对于两个限定类型要兼容,它们必须有相同限定符版本的兼容类型;在说明符或限定符列表中的类型限定符顺序不影响指定的类型。

我认为这使得 doubleconst double 不兼容,所以GCC是正确的。

初始化:

double const * cpd = pd;

在6.5.16.1列表中,这是可以的,因为赋值的约束条件(与初始化有关)。

左操作数具有原子、限定或未限定指针类型,并且(考虑左值转换后左操作数将具有的类型)两个操作数都是指向兼容类型的限定或未限定版本的指针,并且左操作数所指向的类型具有右操作数所指向的类型的所有限定符;

作为可接受的情况之一。cpdpd都指向double的限定版本,而左操作数的目标具有右操作数的所有限定符(以及一个更多的const)。

然而,类型double*const double*不兼容,因此

double const * * cppd = ppd;

再次无效,并需要诊断信息。


它没有解释这个 double const * cpd = pd; // gcc: ok。根据你的研究,double const *double * 是不兼容的,因为它们指向不兼容的类型。但是gcc认为不是这样。 - kotlomoy
2
不,尽管指针类型不兼容,但是将double*赋值给const double*是由6.5.16.1明确允许的,请参见附加说明。 - Daniel Fischer

3

gcc 就在这里,需要进行 C 诊断。

double da[ 3 ] = { 2., 3., 4. };
double (* pda)[ 3 ] = &da;
double const (* cpda)[ 3 ] = pda;  // diagnostic here

基本上您正在尝试将类型为T1的对象分配给类型为T2的对象(仅适用于初始化的简单赋值约束)。

其中T1是指向NT数组的指针。

T2是指向Nconst T数组的指针。

在简单赋值的限制条件中,C语言表示对于指针应满足以下条件(在C99中,6.5.16.1p1):

两个操作数都是指向相容类型的有资格或无资格的指针,并且左侧指向的类型具有右侧指向的类型的所有限定符

例如,这将允许类似于以下内容的东西:

int a = 0;
const int *p = &a;  // p type is a qualified version of &a type

但是在你的例子中,指向由const T组成的数组N的指针不是指向由T组成的数组N的限定版本。在C语言中,数组不能是常数:没有const数组,只有由const元素组成的数组。


有一个错别字:“其中T1是指向由const T组成的N个元素的数组的指针。而T2是指向由T组成的N个元素的数组的指针。” - 应该完全相反。 - kotlomoy

1
这是C和C++之间的区别。在C++中,进行此类型的const转换是完全可以的,但在C中不行。

是的,确实微软的C++在这里不太好比较。我尝试在VS2010中将其编译为C代码(请参见编辑)。但这也是一个不好的比较,因为微软不支持C99。 - kotlomoy
不仅是C和C++之间的区别。在编译C代码时,clang的行为类似于MSVC。 - Doug Richardson

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