Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Visual C++ warn on implicit cast from const void ** to void * in C, but not in C++?

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:

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

  2. 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.

  3. 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?

like image 857
Eliah Kagan Avatar asked May 01 '12 20:05

Eliah Kagan


People also ask

What is const void * in C?

const void is a type which you can form a pointer to. It's similar to a normal void pointer, but conversions work differently. For example, a const int* cannot be implicitly converted to a void* , but it can be implicitly converted to a const void* .

What is a constant void pointer?

A void pointer is a pointer that has no associated data type with it. A void pointer can hold address of any type and can be typecasted to any type.

What is void convert in C++?

void (C++) If a pointer's type is void* , the pointer can point to any variable that's not declared with the const or volatile keyword. A void* pointer can't be dereferenced unless it's cast to another type. A void* pointer can be converted into any other type of data pointer.


2 Answers

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" 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).

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.

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 image 175
6502 Avatar answered Oct 31 '22 17:10

6502


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 a void *, which should be a warning in C and an error in C++.

like image 20
Per Johansson Avatar answered Oct 31 '22 19:10

Per Johansson