为什么 Visual C++ 在 C 中对从 const void ** 到 void * 的隐式转换发出警告,但在 C++ 中却没有?

2022-01-23 00:00:00 visual-studio c visual-c++ constants c++

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

The C/C++ compiler in Microsoft Visual Studio gives warning C4090 when a C program tries to convert a pointer to pointer to const data (like const void ** or const char **) to void * (even though such a type is not actually a pointer to const). Even more strangely, the same compiler silently accepts identical code compiled as C++.

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

What is the reason for this inconsistency, and why does Visual Studio (unlike other compilers) have a problem with implicitly converting a pointer to pointer to const into a void *?

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

I have a C program in which C-strings passed in a variable argument list are read into an array (by a loop in which va_arg is invoked). Since the C-strings are of type const char *, the array that keeps track of them is of type const char **. This array of pointers to strings with const content is itself allocated dynamically (with calloc) and I free it before the function returns (after the C-strings have been processed).

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

When I compiled this code with cl.exe (in Microsoft Visual C++), even with a low warning level, the free call triggered warning C4090. Since free takes a void *, this told me that the compiler didn't like that I had converted a const char ** to a void *. I created a simple example to confirm this, in which I try to convert a const void ** to 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;
}

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

I then compiled it as follows, confirming that this was what triggered the warning:

>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

Microsoft 的 关于警告 C4090 的文档 说:

Microsoft's documentation on warning C4090 says:

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

This warning is issued for C programs. In a C++ program, the compiler issues an error: C2440.

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

That makes sense, since C++ is a more strongly typed language than C, and potentially dangerous implicit casts allowed in C are disallowed in C++. Microsoft's documentation makes it seem like warning C2440 is triggered in C for the same code, or a subset of the code, that would trigger error C2440 in C++.

我是这么想的,直到我尝试将我的测试程序编译为 C++(/TP 标志执行此操作):

Or so I thought, until I tried compiling my test program as C++ (the /TP flag does this):

>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++ 时,不会出现错误或警告.可以肯定的是,我重建了,告诉编译器尽可能积极地发出警告:

When the same code is compiled as C++, no error or warning occurs. To be sure, I rebuilt, telling the compiler to warn as aggressively as possible:

>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 的方式没有问题.

Those builds were with the Microsoft Visual C++ 2010 Express Edition's cl.exe on a Windows 7 machine, but the same errors occur on a Windows XP machine, in both Visual Studio .NET 2003's cl.exe and Visual C++ 2005 Express Edition's cl.exe. So it seems this happens on all versions (though I have not tested on every possible version) and is not a problem with the way Visual Studio is set up on my machines.

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

The same code compiles without a problem in GCC 4.6.1 on an Ubuntu 11.10 system (version string gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1), set to warn as aggressively as possible, as C89, C99, and 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 在被分配后永远不会被读取,但该警告是有意义的并且是不相关的.

It does warn that q is never read from after being assigned, but that warning makes sense and is unrelated.

除了在启用所有警告的情况下不会在 GCC 中触发警告,也不会在 GCC 或 MSVC 中触发 C++ 中的警告,在我看来,从指向 const 的指针转换为 void *根本不应该被认为是一个问题,因为虽然 void * 是指向非 const 的指针,但指向 const 的指针也是指向非 <代码>常量.

Besides not triggering a warning in GCC with all warnings enabled, and not triggering a warning in C++ in either GCC or MSVC, it seems to me that converting from pointer to pointer to const to void * should not be considered a problem at all, because while void * is a pointer to non-const, a pointer to a pointer to const is also a pointer to non-const.

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

In my real-world code (not the example), I can silence this with a #pragma directive, or an explicit cast, or by compiling as C++ (heh heh), or I can just ignore it. But I'd rather not do any of those things, at least not before I understand why this is happening. (And why it doesn't happen in C++!)

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

One possible, partial explanation occurs to me: Unlike C++, C allows implicit casting from void * to any pointer-to-data type. So I could have a pointer implicitly converted from const char ** to void *, and then implicitly converted from void * to char **, thereby making it possible to modify constant data it points to pointers to, without a cast. That would be bad. But I don't see how that is any worse than all sorts of other things that are allowed by C's weaker type-safety.

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

I guess maybe this warning makes sense given the choice not to warn when a non-void pointer type is converted to 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

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

And yet, if that is intentional, then:

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

  1. Why does the Microsoft documentation indicate that code producing this warning in C produces an error in C++?

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

Besides ignoring or suppressing the warning, is there any reasonable alternative, when one must free a non-const pointer to non-const pointer to const data (as in my real-world situation)? If something like this happened in C++, I could store the strings passed in the variable argument list in some high-level STL container instead of an array. For a C program without access to the C++ STL and which doesn't otherwise use high-level collections, that sort of thing is not a reasonable option.

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

Some programmers work under a corporate/organizational policy of treating warnings as errors. C4090 is enabled even with /W1. People must have encountered this before. What do those programmers do?

推荐答案

显然这只是VC++中的一个bug.

Apparently this is simply a bug in VC++.

如果你声明 const char **x; 结果是一个指向 chars 的只读"指针的指针,它本身不是一个只读"指针(我使用术语只读",因为 const-ness 术语提出了错误的概念,即所指向的字符是常量,而这通常是错误的...... const 带有引用和指针是引用或指针的一个属性,它不说明指向或引用数据的常量性.

If you declare const char **x; the result is a pointer to a "read-only" pointer to chars, and it's not itself a "read-only" pointer (I use the term "read-only" because const-ness term pushes the wrong concept that the character being pointed to is constant while this is false in general... const with references and pointers is a property of the reference or of the pointer and tells nothing about constness of the pointed-to or referenced data).

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

Any read/write pointer can be converted to a void * and VC++ has no real reason to emit a warning when compiling that code, neither in C nor in C++ mode.

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

Note that this is not formally a problem because the standard doesn't mandate which warnings should or should not be issued and therefore a compiler is free to emit warnings for perfectly valid code still remaining compliant. VC++ actually emits a plethora of those warnings for valid C++ code...

相关文章