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

Резюме

Компилятор C/C++ в Microsoft Visual Studio выдает предупреждение C4090 , когда программа C пытается преобразовать указатель в указатель на constданные (вроде const void **или отconst char **)до void *(, даже если такой тип на самом деле не является указателем на const). Что еще более странно, тот же компилятор молча принимает идентичный код, скомпилированный как C++.

В чем причина такого несоответствия и почему Visual Studio (, в отличие от других компиляторов ), имеет проблему с неявным преобразованием указателя в указатель на constв void *?

Подробности

У меня есть программа на C, в которой строки C -, переданные в списке переменных аргументов, считываются в массив (циклом, в котором вызывается va_arg). Поскольку строки C -имеют тип const char *, массив, который их отслеживает, имеет тип const char **. Этот массив указателей на строки с содержимым constсам распределяется динамически (с помощьюcalloc)и я freeперед тем, как функция вернет (после того, как строки C -были обработаны ).

Когда я скомпилировал этот код с помощьюcl.exe(в Microsoft Visual C++ ), даже при низком уровне предупреждения вызов freeвызывал предупреждение C4090 . Поскольку freeпринимает void *, это говорит мне о том, что компилятору не понравилось, что я преобразовал 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, и потенциально опасные неявные приведения типов, разрешенные в C, запрещены в C++. В документации Microsoft создается впечатление, что предупреждение C2440 запускается в C для того же кода или подмножества кода, который вызывает ошибку C2440 в C++.

По крайней мере, я так думал, пока не попытался скомпилировать свою тестовую программу как C++ (флаг /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

Когда тот же код компилируется как C++, не возникает ни ошибки, ни предупреждения. Чтобы быть уверенным, я перестроил, сказав компилятору предупреждать как можно агрессивнее.:

>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 C++ 2010 Express Edition cl.exeна компьютере с Windows 7, но те же ошибки возникают на компьютере с Windows XP как в Visual Studio.NET 2003 cl.exe, так и в Visual C++ 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 и 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]

Предупреждает что qникогда не читается после назначения, но это предупреждение имеет смысл и не связано.

Помимо отсутствия срабатывания предупреждения в GCC со всеми включенными предупреждениями и отсутствия срабатывания предупреждения в C++ ни в GCC, ни в MSVC, мне кажется, что преобразование указателя в указатель в const в void *не должно рассматриваться как проблема. вообще, потому что хотя void *является указателем на non-const, указатель на указатель на const также является указателем на non-const.

В моем реальном -мировом коде (не в примере )я могу отключить это с помощью директивы #pragma, или явного приведения, или путем компиляции как C++ (хе-хе ), или я могу просто игнорировать это.Но я бы предпочел не делать ничего из этого, по крайней мере, пока не пойму, почему это происходит. (И почему этого не происходит в C++!)

Мне приходит в голову одно возможное частичное объяснение. :В отличие от C++, C допускает неявное приведение от void *к любому типу данных указателя -на -. Таким образом, я мог бы неявно преобразовать указатель из const char **в void *, а затем неявно преобразовать из void *в char **, тем самым сделав возможным изменение постоянных данных, на которые он указывает, без приведения. Это было бы плохо. Но я не вижу, чем это хуже, чем всевозможные другие вещи, разрешенные более слабой безопасностью типа -C.

Думаю, это предупреждение имеет смысл, учитывая выбор не предупреждать, когда тип указателя, отличного от -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

И все же, если это сделано намеренно, то:

  1. Почему в документации Microsoft указан этот код создание этого предупреждения в C приводит к ошибке в C++?

  2. Помимо игнорирования или подавления предупреждения, есть ли разумная альтернатива, когда нужно freeне-constуказатель на не-constуказатель на constданные (, как в моем реальном -мире ситуация )? Если бы что-то подобное произошло в C++, я мог бы хранить строки, переданные в списке переменных аргументов, в каком-нибудь контейнере STL высокого -уровня вместо массива. Для программы на C, не имеющей доступа к C++ STL и не использующей коллекции высокого -уровня, такой вариант не является разумным.

  3. Некоторые программисты работают в соответствии с корпоративной/организационной политикой обработки предупреждений как ошибок. C4090 активируется даже при /W1. Люди наверняка уже сталкивались с этим. Чем занимаются эти программисты?

18
задан Eliah Kagan 1 May 2012 в 21:04
поделиться