it-swarm-es.tech

¿Por qué no puedo convertir 'char **' a 'const char * const *' en C?

El siguiente fragmento de código (correctamente) muestra una advertencia en C y un error en C++ (usando gcc y g ++ respectivamente, probado con las versiones 3.4.5 y 4.2.1; MSVC no parece importarle):

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

Puedo entender y aceptar esto.
La solución de C++ a este problema es cambiar b para que sea un const char * const *, que no permite la reasignación de los punteros y evita eludir la corrección de const ( Preguntas frecuentes de C++ ).

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

Sin embargo, en C puro, la versión corregida (usando const char * const *) todavía da una advertencia, y no entiendo por qué. ¿Hay alguna forma de evitar esto sin usar un yeso?

Para aclarar:
1) ¿Por qué esto genera una advertencia en C? Debería ser completamente seguro y el compilador de C++ parece reconocerlo como tal.
2) ¿Cuál es la forma correcta de aceptar este char ** como parámetro mientras digo (y hago que el compilador haga cumplir) que no voy a modificar los caracteres a los que apunta? Por ejemplo, si quisiera escribir una función:

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

Y quería invocarlo en un char **, ¿cuál sería el tipo correcto para el parámetro?

Editar: Gracias a quienes respondieron, particularmente a quienes respondieron la pregunta y/o dieron seguimiento a mis respuestas.

He aceptado la respuesta de que lo que quiero hacer no se puede hacer sin un yeso, independientemente de si debería ser posible o no.

59
HappyDude

Tuve este mismo problema hace unos años y me molestó sin fin.

Las reglas en C son más simples (es decir, no enumeran excepciones como convertir char** a const char*const*). En consecuencia, simplemente no está permitido. Con el estándar C++, incluyeron más reglas para permitir casos como este.

Al final, es solo un problema en el estándar C. Espero que el próximo estándar (o informe técnico) aborde esto.

57
Kevin

> Sin embargo, en C puro, esto todavía da una advertencia, y no entiendo por qué

Ya ha identificado el problema: este código no es constante. "Const correct" significa que, excepto const_cast y los lanzamientos de estilo C que eliminan const, nunca puede modificar un objeto const a través de esos punteros o referencias const.

El valor de const-correctness: const está ahí, en gran parte, para detectar errores de programador. Si declaras algo como const, estás afirmando que no crees que deba modificarse, o al menos, aquellos con acceso a la versión const solo no deberían poder modificarlo. Considerar:

void foo(const int*);

Según lo declarado, foo no tiene permiso para modificar el número entero al que apunta su argumento.

Si no está seguro de por qué el código que publicó no es correcto, considere el siguiente código, solo un poco diferente del código de 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;

Los tipos no const solo pueden convertirse a tipos const de formas particulares para evitar cualquier elusión de 'const' en un tipo de datos sin una conversión explícita.

Los objetos inicialmente declarados const son particularmente especiales: el compilador puede asumir que nunca cambian. Sin embargo, si a 'b' se le puede asignar el valor de 'a' sin una conversión, podría intentar inadvertidamente modificar una variable constante. Esto no solo rompería la verificación que le pidió al compilador que hiciera, para evitar que cambie el valor de las variables, ¡también le permitiría romper las optimizaciones del compilador!

En algunos compiladores, esto imprimirá '42', en algunos '43', y otros, el programa se bloqueará.

Editar-agregar:

HappyDude: Tu comentario es perfecto. El lenguaje C o el compilador de C que está utilizando trata a const char * const * de manera fundamentalmente diferente de lo que lo trata el lenguaje C++. Quizás considere silenciar la advertencia del compilador solo para esta línea de origen.

Editar-eliminar: error tipográfico eliminado

10
Aaron

Para ser considerado compatible, el puntero de la fuente debe estar en el nivel de indirección inmediatamente anterior. Entonces, esto le dará la advertencia en GCC:

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

Pero esto no lo hará:

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

Alternativamente, puedes lanzarlo:

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

Necesitaría la misma conversión para invocar la función f() como mencionó. Hasta donde sé, no hay forma de hacer una conversión implícita en este caso (excepto en C++).

10
Fabio Ceconello

Esto es molesto, pero si está dispuesto a agregar otro nivel de redirección, a menudo puede hacer lo siguiente para presionar hacia abajo en el puntero a puntero:

char c = 'c';
char *p = &c;
char **a = &p;

const char *bi = *a;
const char * const * b = &bi;

Tiene un significado ligeramente diferente, pero generalmente es viable, y no usa un yeso.

1
wnoise

No puedo obtener un error al convertir implícitamente char ** a const char * const *, al menos en MSVC 14 (VS2k5) y g ++ 3.3.3. GCC 3.3.3 emite una advertencia, que no estoy exactamente seguro de si es correcto.

prueba.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; 
}

Salida con compilación 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

Salida con compilación 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

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

Salida con g ++: g ++ -Wall test.C

sin salida

0
user7116

Estoy bastante seguro de que la palabra clave const no implica que los datos no se puedan cambiar/sea constante, solo que los datos se tratarán como de solo lectura. Considera esto:

const volatile int *const serial_port = SERIAL_PORT;

que es un código válido. ¿Cómo pueden coexistir volátiles y constantes? Sencillo. volatile le dice al compilador que siempre lea la memoria cuando usa los datos y const le dice al compilador que cree un error cuando se intenta escribir en la memoria usando el puntero serial_port.

¿Const ayuda al optimizador del compilador? No, en absoluto. Debido a que la constidad puede agregarse y eliminarse de los datos a través de la conversión, el compilador no puede determinar si los datos de la constante son realmente constantes (ya que la conversión podría realizarse en una unidad de traducción diferente). En C++ también tiene la palabra clave mutable para complicar aún más las cosas.

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

Lo que sucede cuando se intenta escribir en la memoria que realmente es de solo lectura (ROM, por ejemplo) probablemente no está definido en absoluto en el estándar.

0
Skizz