为什么Visual C ++警告在C中隐式转换从const void **到void *,而不是在C ++中?

摘要

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

这种不一致的原因是什么,为什么Visual Studio(与其他编译器不同)有一个问题,即将指向const的指针隐式转换为void *

细节

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

当我使用cl.exe (在Microsoft Visual C ++中)编译此代码时,即使警告级别较低, free调用也会触发警告C4090 。 由于free取一个void * ,这告诉我编译器不喜欢我将const char **转换为void * 。 我创建了一个简单的例子来证实这一点,我尝试将const 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 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中允许的潜在危险的隐式转换。 微软的文档似乎警告C2440在C中触发相同的代码或代码的子集,这将触发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 

它成功地默默无闻。

这些版本是在Windows 7计算机上使用Microsoft Visual C ++ 2010 Express Edition的cl.exe ,但在Windows XP计算机上,在Visual Studio .NET 2003的cl.exe和Visual C ++ 2005 Express Edition的cl.exe中都会出现相同的错误。 所以这似乎发生在所有版本上(虽然我没有在每个可能的版本上进行测试)并且在我的机器上设置Visual Studio的方式不是问题。

gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 11.10系统(版本字符串gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 )上,相同的代码在GCC 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 *不应该被认为是一个问题,因为void *是指向非const的指针,指向const的指针也是指向非const的指针。

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

我想到了一个可能的部分解释:与C ++不同,C允许从void *隐式转换为任何指向数据的指针类型。 所以我可以将一个指针从const char **隐式转换为void * ,然后从void *隐式转换为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. 除了忽略或抑制警告之外,还有任何合理的替代方法,当必须将非const指针free到非const指针指向const数据时(如在我的实际情况中)? 如果在C ++中发生类似这样的事情,我可以将在变量参数列表中传递的字符串存储在某个高级STL容器中而不是数组中。 对于没有访问C ++ STL且不使用高级集合的C程序,这种事情不是一个合理的选择。

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

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

如果你声明const char **x; 结果是一个指向chars的“只读”指针的指针,它本身不是一个“只读”指针(我使用术语“只读”,因为const -ness术语推出了错误的概念,即该字符是指向是常量,而一般来说这是假的…带引用和指针的const是引用或指针的属性,并且不会告诉指向或引用数据的const )。

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

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

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

我的答案是你应该为免费电话添加一个明确的演员表,然后是一个解释为什么需要它的评论。 编译器中的错误确实发生,使用最简单的解决方法并添加一个注释,以便可以在以后解决该错误时对其进行测试。

还向编译器供应商报告错误的额外点。

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