Dlaczego Visual C ++ ostrzega przed niejawnym rzutowaniem z const void ** na void * w C, ale nie w C ++?

streszczenie

Kompilator C / C ++ w Microsoft Visual Studio ostrzegaC4090 kiedy program C próbuje przekonwertować wskaźnik na wskaźnik doconst dane (jakconst void ** lubconst char **) dovoid * (nawet jeśli taki typ nie jest właściwie wskaźnikiem doconst). Co dziwniejsze, ten sam kompilator po cichu akceptuje identyczny kod skompilowany jako C ++.

Jaka jest przyczyna tej niespójności i dlaczego Visual Studio (w przeciwieństwie do innych kompilatorów) ma problem z niejawną konwersją wskaźnika na wskaźnik doconst w avoid *?

Detale

Mam program C, w którym ciągi C przekazane na zmiennej liście argumentów są odczytywane do tablicy (przez pętlę, w którejva_arg jest wywoływany). Ponieważ struny C są typuconst char *, tablica, która je śledzi, jest typuconst char **. Ta tablica wskaźników do łańcuchów zconst treść jest przydzielana dynamicznie (za pomocącalloc) i jafree to zanim funkcja powróci (po przetworzeniu ciągów C).

Kiedy skompilowałem ten kod za pomocącl.exe (w Microsoft Visual C ++), nawet z niskim poziomem ostrzeżeniafree ostrzeżenie wywołane wywołaniemC4090. Odfree bierzevoid *, powiedział mi, że kompilatorowi się nie podobało, że przekonwertowałemconst char ** do avoid *. Stworzyłem prosty przykład, aby to potwierdzić, w którym próbuję przekonwertowaćconst void ** do avoid *:

<code>/* cast.c - Can a const void** be cast implicitly to void* ? */

int main(void)
{
    const void **p = 0;
    void *q;
    q = p;

    return 0;
}
</code>

Następnie skompilowałem go w następujący sposób, potwierdzając, że to właśnie spowodowało ostrzeżenie:

<code>>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
</code>

Microsoftdokumentacja dotycząca ostrzeżenia C4090 mówi:

To ostrzeżenie jest wydawane dla programów C. W programie C ++ kompilator wyświetla błąd: C2440.

Ma to sens, ponieważ C ++ jest językiem silniej typowanym niż C, a potencjalnie niebezpieczne niejawne obsady dozwolone w C są niedozwolone w C ++. Dokumentacja Microsoftu sprawia, że ​​wygląda na ostrzeżenieC2440 jest wyzwalany w C dla tego samego kodu lub podzbioru kodu, który spowodowałby błądC2440 w C ++.

Albo tak myślałem, dopóki nie spróbowałem skompilować mojego programu testowego jako C ++ (the/TP flaga to robi):

<code>>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
</code>

Gdy ten sam kod jest kompilowany jako C ++, nie występuje błąd ani ostrzeżenie. Aby się upewnić, odbudowałem, mówiąc kompilatorowi, aby ostrzegał tak agresywnie, jak to możliwe:

<code>>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
</code>

Udaje się po cichu.

Te kompilacje były z Microsoft Visual C ++ 2010 Express Editioncl.exe na komputerze z systemem Windows 7, ale te same błędy występują na komputerze z systemem Windows XP w obu programach Visual Studio .NET 2003cl.exe i Visual C ++ 2005 Express Editioncl.exe. Wydaje się, że dzieje się tak na wszystkich wersjach (chociaż nie testowałem na każdej możliwej wersji) i nie stanowi problemu ze sposobem, w jaki Visual Studio jest skonfigurowane na moich komputerach.

Ten sam kod kompiluje się bez problemu w GCC 4.6.1 w systemie Ubuntu 11.10 (ciąg wersji)gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1), aby ostrzec tak agresywnie, jak to możliwe, jak C89, C99 i C ++:

<code>$ 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]
</code>

To ostrzegaq nigdy nie jest czytany po przypisaniu, ale to ostrzeżenie ma sens i nie ma związku.

Poza tym, że nie wywołał ostrzeżenia w GCC ze wszystkimi włączonymi ostrzeżeniami i nie wyzwolił ostrzeżenia w C ++ w GCC lub MSVC, wydaje mi się, że konwertowanie ze wskaźnika na wskaźnik na const navoid * nie powinno być w ogóle uważane za problem, ponieważvoid * jest wskaźnikiem do nieconst, wskaźnik do wskaźnika do const jest również wskaźnikiem do nie-const.

W moim prawdziwym kodzie (nie w przykładzie) mogę to uciszyć za pomocą#pragma dyrektywy, lub jawnej obsady, lub kompilacji jako C ++ (heh heh), lub mogę po prostu zignorować. Ale wolałbym nie robić żadnej z tych rzeczy, przynajmniej zanim zrozumiem, dlaczego tak się dzieje. (I dlaczego tak się nie dzieje w C ++!)

Jedno możliwe, częściowe wyjaśnienie pojawia się dla mnie: W przeciwieństwie do C ++, C pozwala na niejawne rzutowanie zvoid * do dowolnego typu wskaźnik do danych. Mogłem więc skonwertować wskaźnik z niejawnieconst char ** dovoid *, a następnie niejawnie przekonwertowane zvoid * dochar **, dzięki czemu możliwe jest modyfikowanie stałych danych, na które wskazuje wskaźniki, bez rzutowania. To byłoby złe. Ale nie widzę, jak to jest gorsze niż wszystkie inne rzeczy, które są dozwolone przez słabsze bezpieczeństwo typu C.

Sądzę, że może to ostrzeżenie ma sens, biorąc pod uwagę wybór, by nie ostrzegać, gdy nie-void typ wskaźnika jest konwertowany navoid *:

<code>/* cast.c - Can a const void** be cast implicitly to void* ? */

int main(void)
{
    const void **p = 0;
    void *q;
    q = p;

    return 0;
}</code>
<code>>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
</code>

A jednak, jeśli jest to zamierzone, to:

Dlaczego dokumentacja Microsoft wskazuje, że kod generujący to ostrzeżenie w C powoduje błąd w C ++?

Oprócz ignorowania lub stłumienia ostrzeżenia, czy istnieje jakaś rozsądna alternatywa, gdy trzebafree nieconst wskaźnik do nieconst wskaźnik doconst dane (jak w mojej rzeczywistej sytuacji)? Jeśli coś takiego zdarzyło się w C ++, mógłbym przechowywać ciągi przekazane na liście zmiennych argumentów w jakimś kontenerze STL wysokiego poziomu zamiast tablicy. Dla programu C bez dostępu do C ++ STL, który nie używa w inny sposób kolekcji wysokiego poziomu, tego typu rzeczy nie są rozsądną opcją.

Niektórzy programiści pracują w ramach polityki korporacyjnej / organizacyjnej, traktując ostrzeżenia jako błędy.C4090 jest włączony nawet z/W1. Ludzie musieli to wcześniej spotkać. Co robią ci programiści?

questionAnswers(2)

yourAnswerToTheQuestion