Por que não consigo converter ‘char **’ em ‘const char * const *’ em C?

O trecho de código a seguir (corretamente) fornece um aviso em C e um erro em C ++ (usando gcc & g ++ respectivamente, testado com as versões 3.4.5 e 4.2.1; o MSVC não parece se importar):

char **a; const char** b = a; 

Eu posso entender e aceitar isso.
A solução C ++ para esse problema é alterar b para ser um const char * const *, que não permite a redesignação dos pointers e evita que você contorne a const correta ( FAQ C ++ ).

 char **a; const char* const* b = a; 

No entanto, em C puro, a versão corrigida (usando const char * const *) ainda dá um aviso, e eu não entendo o porquê. Existe uma maneira de contornar isso sem usar um casting?

Esclarecer:
1) Por que isso gera um aviso em C? Ele deve ser inteiramente seguro, eo compilador C ++ parece reconhecê-lo como tal.
2) Qual é a maneira correta de aceitar este char ** como um parâmetro ao dizer (e ter o compilador obrigatório) que eu não modificarei os caracteres para os quais ele aponta? Por exemplo, se eu quisesse escrever uma function:

 void f(const char* const* in) { // Only reads the data from in, does not write to it } 

E eu queria invocá-lo em um char **, qual seria o tipo correto para o parâmetro?

Edit: Obrigado para aqueles que responderam, particularmente aqueles que abordaram a questão e / ou acompanharam as minhas respostas.

Aceitei a resposta de que o que eu quero fazer não pode ser feito sem um casting, independentemente de ser ou não possível.

Eu tive esse mesmo problema há alguns anos e me irritou até o fim.

As regras em C são mais simples (isto é, elas não listam exceções como converter char** em const char*const* ). Conseqüentemente, apenas não é permitido. Com o padrão C ++, eles incluíram mais regras para permitir casos como esse.

No final, é apenas um problema no padrão C. Espero que o próximo padrão (ou relatório técnico) abordará isso.

> No entanto, em C puro, isso ainda dá um aviso, e eu não entendo porque

Você já identificou o problema – este código não é const-correto. “Const correto” significa que, exceto para const_cast e estilo C, ao remover const, você nunca pode modificar um object const através desses pointers ou referências.

O valor de const-correctness – const existe, em grande parte, para detectar erros do programador. Se você declarar algo como const, você está afirmando que não acha que deva ser modificado – ou, pelo menos, aqueles com access à versão const só não devem ser capazes de modificá-lo. Considerar:

 void foo(const int*); 

Como declarado, foo não tem permissão para modificar o inteiro apontado pelo seu argumento.

Se você não tem certeza porque o código que você postou não é correto, considere o seguinte código, apenas um pouco diferente do código do HappyDude:

 char *y; char **a = &y; // a points to y const char **b = a; // now b also points to y // const protection has been violated, because: const char x = 42; // x must never be modified *b = &x; // the type of *b is const char *, so set it // with &x which is const char* .. // .. so y is set to &x... oops; *y = 43; // y == &x... so attempting to modify const // variable. oops! undefined behavior! cout < < x << endl; 

Os tipos não-constantes só podem converter para tipos const em formas particulares para evitar qualquer contorno de 'const' em um tipo de dados sem uma conversão explícita.

Os objects inicialmente declarados const são particularmente especiais - o compilador pode assumir que eles nunca mudam. No entanto, se 'b' pode ser atribuído o valor de 'a' sem um casting, então você poderia inadvertidamente tentar modificar uma variável const. Isto não só quebraria o cheque que você pediu ao compilador para fazer, para não permitir que você alterasse o valor das variables ​​- ele também permitiria que você quebrasse as otimizações do compilador!

Em alguns compiladores, isso irá imprimir '42', em algum '43', e outros, o programa irá falhar.

Editar adicionar:

HappyDude: Seu comentário está no local. O compilador C, ou o compilador C que você está usando, trata const char * const * de maneira fundamentalmente diferente da linguagem C ++. Talvez considere silenciar o aviso do compilador somente para esta linha de origem.

Editar-excluir: erro de digitação removido

Para ser considerado compatível, o ponteiro de origem deve ser const no nível indireto imediatamente anterior. Então, isso vai te dar o aviso no GCC:

 char **a; const char* const* b = a; 

Mas isso não vai:

 const char **a; const char* const* b = a; 

Alternativamente, você pode lançá-lo:

 char **a; const char* const* b = (const char **)a; 

Você precisaria do mesmo casting para invocar a function f () como você mencionou. Tanto quanto sei, não há como fazer uma conversão implícita neste caso (exceto em C ++).

Isso é irritante, mas se você estiver disposto a adicionar outro nível de redirecionamento, muitas vezes você pode fazer o seguinte para pressionar o ponteiro para o ponteiro:

 char c = 'c'; char *p = &c; char **a = &p; const char *bi = *a; const char * const * b = &bi; 

Tem um significado ligeiramente diferente, mas geralmente é viável e não usa um casting.

Eu não sou capaz de obter um erro quando implicitamente casting char ** para const char * const *, pelo menos no MSVC 14 (VS2k5) e g ++ 3.3.3. O GCC 3.3.3 emite um aviso, que eu não sei exatamente se está correto em fazer.

test.c:

 #include  #include  void foo(const char * const * bar) { printf("bar %s null\n", bar ? "is not" : "is"); } int main(int argc, char **argv) { char **x = NULL; const char* const*y = x; foo(x); foo(y); return 0; } 

Saída com compilation como código C: cl / TC / W4 / Wp64 test.c

 test.c(8) : warning C4100: 'argv' : unreferenced formal parameter test.c(8) : warning C4100: 'argc' : unreferenced formal parameter 

Saída com compilation como código C ++: cl / TP / W4 / Wp64 test.c

 test.c(8) : warning C4100: 'argv' : unreferenced formal parameter test.c(8) : warning C4100: 'argc' : unreferenced formal parameter 

Saída com gcc: gcc -Wall test.c

 test2.c: In function `main': test2.c:11: warning: initialization from incompatible pointer type test2.c:12: warning: passing arg 1 of `foo' from incompatible pointer type 

Saída com g ++: g ++ -Wall test.C

sem saída

Tenho certeza de que a palavra-chave const não implica que os dados não possam ser alterados / sejam constantes, apenas que os dados serão tratados como somente leitura. Considere isto:

 const volatile int *const serial_port = SERIAL_PORT; 

qual é o código válido. Como pode volátil e const coexistem? Simples. volatile diz ao compilador para sempre ler a memory ao usar os dados e const diz ao compilador para criar um erro quando é feita uma tentativa de gravar na memory usando o ponteiro serial_port.

O const ajuda o otimizador do compilador? Não. De jeito nenhum. Como o constness pode ser adicionado e removido dos dados por meio de conversão, o compilador não pode descobrir se os dados const realmente são constantes (já que o lançamento pode ser feito em uma unidade de tradução diferente). Em C ++, você também tem a palavra-chave mutável para complicar ainda mais as coisas.

 char *const p = (char *) 0xb000; //error: p = (char *) 0xc000; char **q = (char **)&p; *q = (char *)0xc000; // p is now 0xc000 

O que acontece quando é feita uma tentativa de gravar na memory que realmente é somente leitura (ROM, por exemplo) provavelmente não está definida no padrão.