Summary
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++.
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 *
?
Details
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).
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's documentation on warning C4090 says:
This warning is issued for C programs. In a C++ program, the compiler issues an error: 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++.
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
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
It succeeds silently.
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.
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]
It does warn that q
is never read from after being assigned, but that warning makes sense and is unrelated.
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
.
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++!)
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.
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:
Why does the Microsoft documentation indicate that code producing this warning in C produces an error in C++?
Besides ignoring or suppressing the warning, is there any reasonable alternative, when one must
free
a non-const
pointer to non-const
pointer toconst
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.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?
Apparently this is simply a bug in VC++.
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" becauseconst
-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).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 inC
nor inC++
mode.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...
Like 6502 says this seems to be a bug in the compiler. However, you also ask what you should do about it.
My answer is that you should add an explicit cast to the free call, and then a comment explaining why it is needed. Bugs in the compiler do happen, use the easiest workaround and add a note such that it can be tested if the bug has been resolved later.
Extra points for also reporting the bug to the compiler vendor.
As for 1. It seems to be referring to implicitly casting a
const T *
to avoid *
, which should be a warning in C and an error in C++.