Подтвердить что ты не робот

Почему Visual С++ предупреждает о неявном приведении из const void ** в void * в C, но не в С++?

Резюме

Компилятор 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. Люди, должно быть, столкнулись с этим раньше. Что делают эти программисты?

4b9b3361

Ответ 1

По-видимому, это просто ошибка в VС++.

Если вы объявляете const char **x;, результат является указателем на указатель "только для чтения" на символы, и он не является "указателем только для чтения" (я использую термин "только для чтения", потому что const -ness термин подталкивает неправильное понятие о том, что указываемый символ является постоянным, в то время как это вообще ложно... const со ссылками и указателями является свойством ссылки или указателя и ничего не говорит о константе заостренного указателя, к данным или ссылки).

Любой указатель чтения/записи может быть преобразован в void *, а VС++ не имеет реальной причины выдавать предупреждение при компиляции этого кода ни в C, ни в режиме C++.

Обратите внимание, что это формально не проблема, потому что стандарт не предусматривает, какие предупреждения должны или не должны быть выданы, и поэтому компилятор может свободно выдавать предупреждения для совершенно правильного кода, который все еще остается совместимым. VС++ на самом деле испускает множество этих предупреждений для действительного кода на С++...

Ответ 2

Как и 6502, это похоже на ошибку в компиляторе. Однако вы также спрашиваете, что вам следует делать.

Мой ответ заключается в том, что вы должны добавить явное приведение к свободному вызову, а затем комментарий, объясняющий, почему это необходимо. Ошибки в компиляторе происходят, используйте самое легкое обходное решение и добавьте примечание, чтобы его можно было проверить, если ошибка была решена позже.

Дополнительные баллы за сообщение об ошибке для поставщика компилятора.

Как для 1. Кажется, что это подразумевает неявное литье a const T * в void *, которое должно быть предупреждением на C и ошибкой в ​​С++.