18

概括

Microsoft Visual Studio 中的 C/C++ 编译器在C 程序尝试将指向数据的指针(如or )转换为(即使这种类型实际上不是指向 的指针)的指针时发出警告C4090。更奇怪的是,同一个编译器默默地接受编译为 C++ 的相同代码。constconst void **const char **void *const

这种不一致的原因是什么,为什么 Visual Studio(与其他编译器不同)在将指向指针的指针隐式转换constvoid *?

细节

我有一个 C 程序,其中将变量参数列表中传递的 C 字符串读入数组(通过va_arg调用的循环)。由于 C-strings 是 type const char *,因此跟踪它们的数组是 type const char **。这个指向带有内容的字符串的指针数组const本身是动态分配的(使用calloc),并且free在函数返回之前(在处理 C 字符串之后)分配。

当我使用cl.exe(在 Microsoft Visual C++ 中)编译此代码时,即使警告级别较低,free调用也会触发警告C4090。由于free需要 a void *,这告诉我编译器不喜欢我将 a 转换const char **为 a void *。我创建了一个简单的示例来确认这一点,在其中我尝试将 a 转换const void **为 a void *

/* cast.c - Can a const void** be cast implicitly to void* ? */

int main(void)
{
    const void **p = 0;
    void *q;
    q = p;

    return 0;
}

然后我将其编译如下,确认这是触发警告的原因:

>cl cast.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

cast.c
cast.c(7) : warning C4090: '=' : different 'const' qualifiers
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:cast.exe
cast.obj

微软关于警告 C4090 的文档说:

此警告针对 C 程序发出。在 C++ 程序中,编译器发出错误:C2440。

这是有道理的,因为 C++ 是一种比 C 更强类型的语言,并且 C 中允许的潜在危险的隐式转换在 C++ 中是不允许的。Microsoft 的文档看起来像是在 C 中针对相同代码或代码子集触发了C2440警告,这将在 C++ 中触发错误C2440 。

或者我是这么想的,直到我尝试将我的测试程序编译为 C++(/TP标志这样做):

>cl /TP cast.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

cast.c
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:cast.exe
cast.obj

将相同的代码编译为 C++ 时,不会出现错误或警告。可以肯定的是,我重建了,告诉编译器尽可能积极地发出警告:

>cl /TP /Wall cast.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

cast.c
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:cast.exe
cast.obj

它默默地成功了。

这些构建是cl.exe在 Windows 7 机器上使用 Microsoft Visual C++ 2010 Express Edition 的,但在 Windows XP 机器上,Visual Studio .NET 2003cl.exe和 Visual C++ 2005 Express Edition 的cl.exe. 因此,这似乎发生在所有版本上(尽管我没有在每个可能的版本上进行测试),并且在我的机器上设置 Visual Studio 的方式不是问题。

相同的代码在 Ubuntu 11.10 系统(版本字符串)上的 GCC 4.6.1 中编译没有问题gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1,设置为尽可能积极地警告,如 C89、C99 和 C++:

$ gcc -ansi -pedantic -Wall -Wextra -o cast cast.c
cast.c: In function ‘main’:
cast.c:6:11: warning: variable ‘q’ set but not used [-Wunused-but-set-variable]

$ gcc -std=c99 -pedantic -Wall -Wextra -o cast cast.c
cast.c: In function ‘main’:
cast.c:6:11: warning: variable ‘q’ set but not used [-Wunused-but-set-variable]

$ g++ -x c++ -ansi -pedantic -Wall -Wextra -o cast cast.c
cast.c: In function ‘int main()’:
cast.c:6:11: warning: variable ‘q’ set but not used [-Wunused-but-set-variable]

它确实警告q在分配后永远不会被读取,但该警告是有意义的并且是不相关的。

除了在启用所有警告的情况下不会在 GCC 中触发警告,也不会在 GCC 或 MSVC 中触发 C++ 中的警告,在我看来,从指针转换为指向 const 的指针void *根本不应该被视为问题,因为 whilevoid *是指向 non-const的指针,指向 const 的指针也是指向 non- 的指针const

在我的实际代码(不是示例)中,我可以使用#pragma指令或显式强制转换,或编译为 C++(呵呵),或者我可以忽略它。但我宁愿不做任何这些事情,至少在我明白为什么会发生之前不做。(以及为什么它不会在 C++ 中发生!)

我想到了一种可能的部分解释:与 C++ 不同,C 允许隐式转换void *为任何指向数据的指针类型。所以我可以有一个指针从 to 隐式转换const char **void *然后从void *to隐式转换char **,从而可以修改它指向的指针的常量数据,而无需强制转换。那会很糟糕。但我不认为这比 C 较弱的类型安全所允许的所有其他事情更糟糕。

我想这个警告可能是有意义的,因为当非void指针类型转换为时选择不发出警告void *

/* cast.c - Can a const void** be cast implicitly to void* ? */

int main(void)
{
    const void **p = 0;
    void *q;
    q = p;

    return 0;
}
>cl /Wall voidcast.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

voidcast.c
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:voidcast.exe
voidcast.obj

然而,如果这是故意的,那么:

  1. 为什么 Microsoft 文档表明在 C 中产生此警告的代码会在 C++ 中产生错误?

  2. 除了忽略或抑制警告之外,是否有任何合理的选择,当必须free使用非const指针指向非const指向const数据的指针时(如在我的实际情况中)?如果在 C++ 中发生这样的事情,我可以将变量参数列表中传递的字符串存储在一些高级 STL 容器中,而不是数组中。对于无法访问 C++ STL 并且不使用高级集合的 C 程序,这种事情不是一个合理的选择。

  3. 一些程序员在将警告视为错误的公司/组织政策下工作。C4090即使使用/W1. 人们以前一定遇到过这种情况。那些程序员是做什么的?

4

2 回答 2

11

显然这只是 VC++ 中的一个错误。

如果您声明const char **x;结果是指向字符的“只读”指针的指针,并且它本身不是“只读”指针(我使用术语“只读”,因为const-ness 术语推动了错误的概念,即被指向的字符是常量,而这通常是错误的......const引用和指针是引用或指针的属性,并且没有说明指向或引用数据的常量性)。

任何读/写指针都可以转换为 avoid *并且 VC++ 在编译该代码时没有真正的理由发出警告,无论C是在模式下还是C++模式下。

请注意,这不是正式的问题,因为该标准没有规定应该或不应该发出哪些警告,因此编译器可以自由地为仍然保持合规的完全有效的代码发出警告。VC++ 实际上会针对有效的 C++ 代码发出大量警告...

于 2012-05-01T20:34:12.743 回答
1

就像 6502 所说的,这似乎是编译器中的一个错误。但是,您也会问您应该怎么做。

我的回答是,您应该在免费通话中添加明确的演员表,然后添加注释来解释为什么需要它。编译器中确实会发生错误,请使用最简单的解决方法并添加注释,以便以后可以测试该错误是否已解决。

还将错误报告给编译器供应商的额外积分。

至于 1. 它似乎是指将 a 隐式转换const T *为 a void *,这应该是 C 中的警告和 C++ 中的错误。

于 2012-05-01T21:23:36.783 回答