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
freea non-constpointer to non-constpointer toconstdata (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…constwith 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 inCnor 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…