Por que o Visual C ++ avisa sobre conversão implícita de const void ** para void * em C, mas não em C ++?

Resumo

O compilador C / C ++ no Microsoft Visual Studio dá avisoC4090 quando um programa C tenta converter um ponteiro para ponteiro paraconst dados (comoconst void ** ouconst char **) paravoid * (mesmo que tal tipo não seja realmente um ponteiro paraconst). Ainda mais estranhamente, o mesmo compilador silenciosamente aceita código idêntico compilado como C ++.

Qual é a razão para essa inconsistência, e por que o Visual Studio (ao contrário de outros compiladores) tem um problema com a conversão implícita de um ponteiro para ponteiro paraconst dentro devoid *?

Detalhes

Eu tenho um programa C em que C-strings passados ​​em uma lista de argumentos variáveis ​​são lidos em uma matriz (por um loop em queva_arg é invocado). Já que as cadeias C são do tipoconst char *, o array que os controla é do tipoconst char **. Esta matriz de ponteiros para seqüências de caracteres comconst conteúdo é alocado dinamicamente (comcalloc) e eufree antes que a função retorne (depois que as seqüências C forem processadas).

Quando eu compilei este código comcl.exe (no Microsoft Visual C ++), mesmo com um baixo nível de aviso, ofree aviso acionado por chamadaC4090. Desde afree Leva umvoid *, isso me disse que o compilador não gostou que eu tivesse convertido umconst char ** para umvoid *. Eu criei um exemplo simples para confirmar isso, no qual eu tento converter umconst void ** para umvoid *:

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

Eu então compilei da seguinte forma, confirmando que foi isso que desencadeou o aviso:

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

Da Microsoftdocumentação sobre aviso C4090 diz:

Este aviso é emitido para programas em C. Em um programa C ++, o compilador emite um erro: C2440.

Isso faz sentido, já que C ++ é uma linguagem mais fortemente tipada que C, e invenções potencialmente perigosas permitidas em C não são permitidas em C ++. Documentação da Microsoft faz parecer avisoC2440 é disparado em C para o mesmo código, ou um subconjunto do código, que acionaria o erroC2440 em C ++.

Ou então eu pensei, até que eu tentei compilar meu programa de teste como C ++ (o/TP flag faz isso):

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

Quando o mesmo código é compilado como C ++, nenhum erro ou aviso ocorre. Para ter certeza, eu reconstruí, dizendo ao compilador para avisar da forma mais agressiva possível:

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

Ele sucede silenciosamente.

Essas compilações foram com o Microsoft Visual C ++ 2010 Express Editioncl.exe em uma máquina com Windows 7, mas os mesmos erros ocorrem em uma máquina com Windows XP, tanto no Visual Studio .NET 2003cl.exe e Visual C ++ 2005 Express Editioncl.exe. Por isso, parece que isso acontece em todas as versões (embora eu não tenha testado em todas as versões possíveis) e não é um problema com a maneira como o Visual Studio está configurado em minhas máquinas.

O mesmo código compila sem um problema no GCC 4.6.1 em um sistema Ubuntu 11.10 (string de versãogcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1), definido para avisar o mais agressivamente possível, como C89, C99 e 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>

Adverte queq nunca é lido depois de ser atribuído, mas esse aviso faz sentido e não está relacionado.

Além de não disparar um aviso no GCC com todos os avisos habilitados, e não disparar um aviso em C ++ em GCC ou MSVC, parece-me que converter de ponteiro em ponteiro para constvoid * não deve ser considerado um problema, porque enquantovoid * é um indicador para nãoconst, um ponteiro para um ponteiro para const também é um ponteiro para nãoconst.

No meu código do mundo real (não o exemplo), posso silenciar isso com um#pragma diretiva, ou um elenco explícito, ou compilando como C ++ (heh heh), ou eu posso simplesmente ignorá-lo. Mas prefiro não fazer nenhuma dessas coisas, pelo menos não antes de entender por que isso está acontecendo. (E por que isso não acontece em C ++!)

Uma possível explicação parcial ocorre para mim: Ao contrário de C ++, C permite a conversão implícita devoid * para qualquer tipo de ponteiro para dados. Então, eu poderia ter um ponteiro implicitamente convertido deconst char ** paravoid *e, em seguida, implicitamente convertido devoid * parachar **, tornando assim possível modificar dados constantes para os apontadores, sem um elenco. Isso seria ruim. Mas eu não vejo como isso é pior do que todos os tipos de outras coisas que são permitidas pela segurança mais fraca do tipo C.

Eu acho que talvez este aviso faça sentido, dada a escolha de não avisar quando umvoid tipo de ponteiro é convertido paravoid *:

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

E, no entanto, se isso for intencional, então:

Por que a documentação da Microsoft indica que o código que produz este aviso em C produz um erro em C ++?

Além de ignorar ou suprimir o aviso, existe alguma alternativa razoável, quando se devefree um nãoconst ponteiro para nãoconst ponteiro paraconst dados (como na minha situação do mundo real)? Se algo assim acontecesse em C ++, eu poderia armazenar as cadeias passadas na lista de argumentos variáveis ​​em algum contêiner STL de alto nível em vez de uma matriz. Para um programa C sem acesso ao C ++ STL e que não utiliza coletas de alto nível, esse tipo de coisa não é uma opção razoável.

Alguns programadores trabalham sob uma política corporativa / organizacional de tratar avisos como erros.C4090 é ativado mesmo com/W1. As pessoas devem ter encontrado isso antes. O que esses programadores fazem?

questionAnswers(2)

yourAnswerToTheQuestion