it-swarm-pt.tech

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

O seguinte trecho de código (corretamente) emite um aviso em C e um erro em C++ (usando gcc & g ++, respectivamente, testados nas versões 3.4.5 e 4.2.1; o MSVC parece não 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 *, o que desabilita a reatribuição dos ponteiros e evita que você evite a correção da const ( C++ FAQ ).

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

Esclarecer:
1) Por que isso gera um aviso em C? Ele deve ser totalmente seguro e o compilador C++ parece reconhecê-lo como tal.
2) Qual é a maneira correta de aceitar esse caractere ** como parâmetro enquanto diz (e faz o compilador aplicar) que não modificarei os caracteres para os quais ele aponta? Por exemplo, se eu quis escrever uma função:

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

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

Edit: Obrigado a todos que responderam, principalmente aqueles que responderam à pergunta e/ou acompanharam minhas respostas.

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

59
HappyDude

Eu tive esse mesmo problema há alguns anos e isso me irritou sem fim.

As regras em C são mais simplesmente indicadas (ou seja, elas não listam exceções como converter char** para const char*const*). Consequentemente, simplesmente não é permitido. Com o padrão C++, eles incluíram mais regras para permitir casos como este.

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

57
Kevin

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

Você já identificou o problema - este código não está correto. "Const correct" significa que, exceto para const_cast e conversão de estilo C removendo const, você nunca pode modificar um objeto const através desses ponteiros ou referências const.

O valor da const-correctness - const existe, em grande parte, para detectar erros do programador. Se você declarar algo como const, está afirmando que não acha que deve ser modificado - ou pelo menos aqueles que têm acesso à versão const apenas não devem poder modificá-lo. Considerar:

void foo(const int*);

Conforme declarado, foo não tem permissão para modificar o número inteiro apontado por seu argumento.

Se você não sabe ao certo por que o código que você postou não está correto, considere o código a seguir, 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;

Tipos não const só podem ser convertidos em tipos const de maneiras específicas para impedir qualquer contornar 'const' em um tipo de dados sem uma conversão explícita.

Os objetos inicialmente declarados const são particularmente especiais - o compilador pode assumir que nunca muda. No entanto, se 'b' puder ser atribuído ao valor de 'a' sem uma conversão, você poderá inadvertidamente tentar modificar uma variável const. Isso não apenas interromperia a verificação que você solicitou ao compilador, como também impediria que você alterasse o valor das variáveis ​​- também permitiria interromper as otimizações do compilador!

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

Editar-adicionar:

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

Editar-excluir: erro de digitação removido

10
Aaron

Para ser considerado compatível, o ponteiro da fonte deve ser const no nível de indireção imediatamente anterior. Portanto, isso fornecerá o aviso no GCC:

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

Mas isso não vai:

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

Como alternativa, você pode transmiti-lo:

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

Você precisaria da mesma conversão para invocar a função f() como você mencionou. Até onde eu sei, não há como fazer uma conversão implícita nesse caso (exceto em C++).

10
Fabio Ceconello

Isso é irritante, mas se você deseja adicionar outro nível de redirecionamento, muitas vezes pode fazer o seguinte para Empurrar para baixo no 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 elenco.

1
wnoise

Não consigo obter um erro ao converter implicitamente char ** para const char * const *, pelo menos no MSVC 14 (VS2k5) eg g ++ 3.3.3. O GCC 3.3.3 emite um aviso, que não sei exatamente se está correto.

test.c:

#include <stdlib.h> 
#include <stdio.h>
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 compilação 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 compilação 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

0
user7116

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;

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

O const ajuda o otimizador do compilador? Não. Não mesmo. Como a constness pode ser adicionada e removida dos dados por meio da conversão, o compilador não pode descobrir se os dados const realmente são constantes (já que a conversão pode ser feita em uma unidade de conversão diferente). No C++, você também tem a palavra-chave mutável para complicar ainda mais.

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 memória que realmente é somente leitura (ROM, por exemplo) provavelmente não está definida no padrão.

0
Skizz