Вопрос по c++, visual-studio, visual-c++, c – Почему Visual C ++ предупреждает о неявном приведении из const void ** к void * в C, но не в C ++?

18
Summary

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

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

Details

У меня есть 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 & APOS; sдокументация по предупреждению C4090 говорит:

This warning is issued for C programs. In a C++ program, the compiler issues an error: 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 2003cl.exe и Visual C ++ 2005 Express Editioncl.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 * это указатель наconstуказатель на указатель на const также является указателем на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

И все же, если это умышленно, то:

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

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?

Потому что Visual C ++ тупой. Seth Carnegie

Ваш Ответ

2   ответа
1

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

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

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

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

Переключение с такой прагмы добавляет риск того, что вы забудете снова выключить переключатель, возможно, когда код подвергается рефакторингу / копированию. Явное приведение - это временное решение без состояния. Но если вы действительно предпочитаете прагму, тогда, конечно, продолжайте. Важная часть заключается в том, чтобы пометить его комментарием, чтобы люди, читающие код, знали, почему он нуждается в особой обработке.
Почему вы считаете явное приведение в свободном вызове предпочтительнее#pragmaвокруг бесплатного звонка отключение (и повторное включение) предупреждения? (На самом деле я бы использовалpush а такжеpop синтаксис, а не явное повторное включение предупреждения.) Eliah Kagan
Но проблема зависит от поставщика. Так что я могу окружить прагмы#ifdef _MSC_VER а также#endif директивы. Eliah Kagan
@EliahKagan: при использовании кода код останется на 100% переносимым. Прагмы зависят от поставщика.
11

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

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

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

Похожие вопросы