Резюме
Компилятор C/С++ в Microsoft Visual Studio предупреждает C4090, когда программа C пытается преобразовать указатель на указатель на данные const
(например, const void **
или const char **
) до void *
(даже если такой тип не является указателем на const
). Еще более странно, один и тот же компилятор молча принимает идентичный код, скомпилированный как С++.
В чем причина этой несогласованности и почему Visual Studio (в отличие от других компиляторов) имеет проблему с неявным преобразованием указателя в указатель на const
в void *
?
Подробнее
У меня есть программа на C, в которой C-строки, переданные в списке переменных аргументов, считываются в массив (по циклу, в котором вызывается va_arg
). Поскольку C-строки имеют тип const char *
, массив, который отслеживает их, имеет тип const char **
. Этот массив указателей на строки с содержимым const
сам распределяется динамически (с calloc
) и я free
до того, как функция вернется (после обработки C-строк).
Когда я скомпилировал этот код с помощью cl.exe
(в Microsoft Visual С++), даже при низком уровне предупреждения, free
вызывал предупреждение C4090. Так как free
принимает a void *
, это говорит мне, что компилятору не понравилось, что я преобразовал a 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
Microsoft документация по предупреждению C4090 говорит:
Это предупреждение выдается для программ на C. В программе на С++ компилятор выдает ошибку: C2440.
Это имеет смысл, поскольку С++ - это более строго типизированный язык, чем C, и потенциально опасные неявные броски, разрешенные в C, запрещены в С++. Документация Microsoft делает это похоже на предупреждение C2440 запускается в C для того же кода или подмножества кода, что приведет к ошибке C2440 на С++.
Или так я думал, пока я не попытался скомпилировать мою тестовую программу как С++ (это делает флаг /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
Когда тот же код скомпилирован как С++, ошибки и предупреждения не происходит. Разумеется, я перестроился, сообщив компилятору как можно более агрессивно предупреждать:
>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
Он успешно завершает работу.
Эти сборки были с Microsoft Visual С++ 2010 Express Edition cl.exe
на компьютере под управлением Windows 7, но те же ошибки возникают на компьютере под управлением Windows XP, как в Visual Studio.NET 2003 cl.exe
, так и в Visual С++ 2005 Express Edition cl.exe
. Таким образом, похоже, что это происходит во всех версиях (хотя я не тестировал каждую возможную версию), и это не проблема с тем, как Visual Studio настроена на моих машинах.
Тот же самый код компилируется без проблем в GCC 4.6.1 в системе Ubuntu 11.10 (строка версии gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1
), устанавливается как можно более агрессивно, как C89, C99 и С++:
$ 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, мне кажется, что преобразование с указателя на указатель на const в void *
не должно рассматриваться как проблема вообще говоря, поскольку while void *
является указателем на не const
, указатель на указатель на const также является указателем на не const
.
В моем коде реального мира (а не в примере) я могу заставить замолчать это с помощью директивы #pragma
или явного приведения в действие или компиляции как С++ (heh heh), или я могу просто проигнорировать ее. Но я бы предпочел не делать ничего из этого, по крайней мере, до того, как я пойму, почему это происходит. (И почему этого не происходит в С++!)
Мне представляется одно возможное, частичное объяснение: в отличие от С++, C допускает неявное кастинг от void *
к любому типу указателя к типу данных. Поэтому я мог бы иметь указатель, неявно преобразованный из const char **
в void *
, а затем неявно преобразованный из void *
в char **
, тем самым позволяя изменять постоянные данные, которые он указывает на указатели, без трансляции. Это было бы плохо. Но я не вижу, как это хуже, чем всевозможные другие вещи, которые допускаются более слабым типом безопасности.
Я думаю, возможно, это предупреждение имеет смысл, учитывая выбор не предупреждать, когда тип указателя не 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
И все же, если это намеренно, то:
-
Почему в документации Microsoft указывается, что код, создающий это предупреждение в C, вызывает ошибку в С++?
-
Кроме игнорирования или пресечения предупреждения, существует ли разумная альтернатива, когда нужно
free
указатель неconst
указатель наconst
указатель наconst
данные (как в моем реальном режиме, мировая ситуация)? Если бы что-то подобное произошло в С++, я мог бы хранить строки, переданные в списке аргументов переменных, в каком-то контейнере STL высокого уровня вместо массива. Для программы C без доступа к С++ STL и которая в противном случае не использует высокоуровневые коллекции, такая вещь не является разумным вариантом. -
Некоторые программисты работают в рамках корпоративной/организационной политики обработки предупреждений как ошибок. C4090 включен даже с
/W1
. Люди, должно быть, столкнулись с этим раньше. Что делают эти программисты?