Por que o Visual C ++ avisa sobre conversão implícita de const void ** para void * em C, mas não em C ++?
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 *
?
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 *
:
/* cast.c - Can a const void** be cast implicitly to void* ? */
int main(void)
{
const void **p = 0;
void *q;
q = p;
return 0;
}
Eu então compilei da seguinte forma, confirmando que foi isso que desencadeou o aviso:
>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
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):
>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
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:
>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
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 ++:
$ 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]
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 *
:
/* 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
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?