2008-09-16 38 views
55

El siguiente fragmento de código (correctamente) da una advertencia en C y un error en C++ (usando gcc & g ++ respectivamente, probado con versiones 3.4.5 y 4.2.1; MSVC no parece Care):¿Por qué no puedo convertir 'char **' a 'const char * const *' en C?

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 que eluda la corrección de const (C++ FAQ).

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

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

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 carácter ** como parámetro mientras digo (y hacer que el compilador haga cumplir) que no voy a modificar los caracteres a los que apunta? Por ejemplo, si quería escribir una función:

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

Y quería invocar en un char **, lo que sería el tipo correcto para el parámetro?

Edit: Gracias a quienes han respondido, especialmente a los que 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 molde, independientemente de si es posible o no.

+0

Estoy confundido en cuanto a su pregunta, es: "¿Cómo hago para que C no me advierta?" o es "¿Cómo puedo obtener C++ para no lanzar un error de compilación?" O tal vez no sea ninguno de estos. – user7116

+0

Estoy de acuerdo con sixlettervariables, esta pregunta necesita más aclaraciones. –

+0

La pregunta es "¿por qué no puedo convertir 'char **' en 'char * const *?" – Kevin

Respuesta

53

Tuve este mismo problema hace unos años y me molestó muchísimo.

Las reglas en C son más simples (es decir, no enumeran excepciones como la conversión de char** a const char*const*). 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 la próxima norma (o informe técnico) aborde esto.

+0

¿Por qué votaron negativamente? ¡Por favor explique! – Kevin

+0

Ciertamente no lo voté; es la respuesta más relevante que he recibido hasta ahora. Si tuviera 100 puntos de reputación, votaría esto. – HappyDude

+1

@Kevin: Si todavía está por aquí, me pregunto qué tan seguro está acerca de su respuesta que es solo un problema en el estándar. ¿En ese momento revisó el estándar para verificarlo? Si es así, prácticamente puedo cerrar esta pregunta, ya que esta es una respuesta tanto como la que voy a obtener. – HappyDude

11

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

Usted ya ha identificado el problema - este código no es const-corrección. "Const correct" significa que, a excepción de los moldes const_cast y C-style que eliminan const, nunca se puede modificar un objeto const a través de esos punteros o referencias const.

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

void foo(const int*); 

Como declaró, foo no tiene permiso para modificar el número entero apuntado por su argumento.

Si no está seguro de por qué el código que envió no es const-corrección, considere el siguiente código, sólo ligeramente distinto al código de HappyDude:

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

no constante sólo puede convertir a const tipos de formas particulares para evitar cualquier elusión de 'const' en un tipo de datos sin un molde explícito.

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 un molde, entonces podría intentar modificar una variable const sin querer. Esto no solo rompería el control que le pediste al compilador, sino que te impediría cambiar el valor de las variables, ¡también te permitiría romper las optimizaciones del compilador!

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

Editar-add:

HappyDude: Tu comentario es en el clavo. O bien, el C langauge, o el compilador de C que está utilizando, trata const char * const * fundamentalmente de manera diferente que el lenguaje C++ lo trata. Tal vez considere silenciar la advertencia del compilador solo para esta línea fuente.

Editar-Borrar: errata eliminado

+4

Lo que ha dicho es correcto, pero no aborda la pregunta. Como dije, entiendo por qué la conversión de un char ** a un const char ** es incorrecta. Lo que no entiendo es por qué la conversión de un char ** a un const char * const * es incorrecta. He actualizado mi pregunta para dilucidar. – HappyDude

+3

Es interesante que esta respuesta siga recibiendo votos. Supongo que mucha gente no lee más allá de las primeras líneas de la pregunta, algo que tendré en cuenta para el futuro. Aunque esta es una publicación bastante bien escrita en un punto relativamente oscuro, solo desearía que fuera relevante: P. – HappyDude

+3

@ Aaron: su publicación no es particularmente relevante para el tema, pero está bien escrita. – user7116

0

No soy capaz de obtener un error al lanzar 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.

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

de 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 

de 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

hay salida

+0

Lo siento, debería haber aclarado que estoy compilando con gcc. He actualizado mi pregunta para aclarar esto. Afortunadamente (¿o quizás no?) Rara vez me encuentro con muchas advertencias difíciles de manejar en msvc. – HappyDude

+0

@HappyDude: Actualicé mis hallazgos de GCC. Y ahora veo lo que dices, pero todavía no estoy de acuerdo con la advertencia del compilador. Voy a dividir mi especificación C y ver lo que encuentro. – user7116

+0

@sixlettervariables: ¡Gracias por la respuesta! Avíseme si encuentra algo, pero sospecho que lo que Kevin dijo era correcto y simplemente no está cubierto por el estándar. – HappyDude

0

Estoy bastante seguro de que la palabra clave const no implica que los datos no se puede cambiar/es constante, solo que los datos serán tratados como de solo lectura. Considere esto:

const volatile int *const serial_port = SERIAL_PORT; 

que es un código válido. ¿Cómo pueden convivir volátiles y const? Sencillo. volátil 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.

¿Ayuda const el optimizador del compilador? No, en absoluto. Debido a que la constness se puede agregar y eliminar de los datos a través del casting, el compilador no puede determinar si los datos de const realmente son constantes (dado que el molde se puede hacer 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 

¿Qué pasa cuando se hace un intento de escribir en la memoria que realmente es de sólo lectura (ROM, por ejemplo) probablemente no está definido en la norma en absoluto.

+2

Esto realmente no aborda la pregunta. Según tengo entendido, eliminar const mediante casting es técnicamente una operación indefinida según el estándar C, aunque la mayoría de los compiladores lo permitirán y 'harán lo correcto'. Pero no es de lo que estoy preguntando en absoluto. – HappyDude

+2

@HappyDude fundir const está bien definido. No se definiría si intentas escribir en un objeto declarado como 'const' (o no escribible, como un literal de cadena). –

9

Para que se considere compatible, el puntero fuente debe estar const en el nivel de direccionamiento indirecto anterior. Por lo tanto, esto le dará la advertencia en GCC:

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

Pero esto no:

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

Como alternativa, puede echarlo:

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

podría necesitar el mismo lanzar para invocar la función f() como mencionaste. Hasta donde yo sé, no hay forma de hacer una conversión implícita en este caso (excepto en C++).

+0

Lea la pregunta con más cuidado. Soy consciente de que el primer fragmento de código es incorrecto, como digo en la línea que lo sigue inmediatamente. El segundo, sin embargo, debería estar bien (hasta donde yo sé). Como he mencionado, MSVC no da una advertencia, pero lo hace gcc. – HappyDude

+0

Disculpe, no le expliqué bien lo que quería, y tampoco probé el resultado en GCC para asegurarme de que las muestras del código fueran precisas. De corrección. –

+0

Gracias Fabio, tu respuesta editada es mucho más clara. Creo que voy a tener que aceptar que necesito lanzarlo. – HappyDude

1

Esto es molesto, pero si usted está dispuesto a añadir otro nivel de redirección, a menudo se puede hacer lo siguiente para empujar hacia abajo en el puntero del puntero-a-:

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

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

Tiene un poco diferente es decir, pero usualmente funciona y no usa un yeso.