¿Por qué pasar char ** como const char ** genera una advertencia?

He estado recibiendo esta advertencia:

note: expected 'const char **' but argument is of type 'char **' 

Por ahora, paso los argumentos al const char ** en const char ** . ¿Hay alguna otra forma en que pueda deshacerme de eso?

Respuesta corta

¿Puedes encasillar de forma segura el char ** para const char** ? No (No es seguro de todos modos), y la razón es mucho más sutil de lo que piensas. ¿Puedes deshacerte de otra manera? Por supuesto. Cargue una matriz de valores const char* partir de sus valores char* y pase eso en su lugar. (o cambie el prototipo de callee, pero eso es trampa = P).

Considere el siguiente código, que básicamente hace todo lo que desea excepto invocar una función. La línea marcada demuestra el punto de lanzamiento equivalente

 const char *s = "Test"; char *p = NULL; char **pp = &p; // Put address of our pointer in our pointer-to-pointer. const char **cpp = pp; // Here: assigning char** to const char** *cpp = s; // perfectly legal; pp and s both finish "char const" *p = 0; // ru ro raggy 

Me toma un tiempo mirar esto realmente, y ciertamente tampoco lo vi al principio. @sheu hizo un trabajo sólido de atraparlo unas 24 horas antes de que realmente lo pensara lo suficiente como para darme cuenta de que tenía razón todo el tiempo (y de hecho voté por encima de esa respuesta antes de escribir ésta). Entonces pensé que estaba equivocado al mismo tiempo que pensaba que su respuesta no era aplicable. Resulta que ambos estábamos equivocados en ese salto, porque tenía razón la primera vez, me equivoqué la segunda vez, y ahora … ugh.

En VS2012 y VS2010, tanto la línea marcada marcará un error sin un molde. clang comstackrá con una advertencia en C, pero lo permite (lo cual me pareció sorprendente). Teniendo en cuenta, realmente tienes que salir de tu lugar feliz para romperlo, pero aún así está roto.

El rest de esto es una diatriba sobre la identificación de tipos de punteros, su constness y lo que es equivalente a qué.


Larga diatriba en punteros y const

La advertencia es porque char ** y const char ** no son equivalentes (duh). Para estar en lo correcto, podrías arreglar el prototipo (llamado) o arreglar el llamador (cargando una matriz de const char * y pasando eso). ¿Pero puede encasillar de forma segura el primero al segundo? Hmmm ….

Recuerde, por la const estándar va al elemento inmediatamente a la izquierda . Declararlo en la parte más a la izquierda de un tipo de datos es una elegancia que el lenguaje admite, pero a menudo presenta confusión o problemas. Como regla general, si const aparece en el extremo izquierdo de un decl inmediatamente antes del tipo, se aplica al tipo de datos; no el puntero siguiente (si hay alguno). Cuando aparece a la derecha de cualquier cosa, se aplica a la parte de declive izquierda inmediata, ya sea una parte de tipo de datos o una parte de puntero, pero no importa lo que aplique solo a una sola parte.

Una plétora de muestras sigue:

Sin Indirección :

 const char ch; // const character. must be initialized. char const ch; // same as above 

Single-Indirection :

 char *p; // p is mutable, *p is mutable const char *p; // p is mutable, *p is const char const *p; // same as above. char *const p; // p is const, *p is mutable, must be initialized. char const *const p; // p is const, *p is const, must be initialized. 

Doble Indirección :

 char **p; // ptr-to-ptr-to-char // p, *p, and **p are ALL mutable const char **p; // ptr-to-ptr-to-const-char // p and *p are mutable, **p is const char const **p; // same as above char *const *p; // ptr-to-const-ptr-to-char // p is mutable, *p is const, **p is mutable. char **const p; // const-ptr-to-ptr-to-char // p is const, *p is mutable, **p is mutable. // must be initialized. const char **const p; // const-ptr-to-ptr-to-const-char // p is const, *p is mutable, **p is const. // must be initialized. char const **const p; // same as above char const *const *p; // ptr-to-const-ptr-to-const-char // p is mutable, *p is const, **p is const. const char *const *p; // same as above. char *const *const p; // const-ptr-to-const-ptr-to-char // p is const, *p is const, **p is mutable. // must be initialized. 

Y, por supuesto, quién puede salir de casa sin …

 char const *const *const p; // const-ptr-to-const-ptr-to-const-char // everything is const. // must be initialized. const char *const *const p; // same as above 

Entonces, ¿cómo afecta esto tu pregunta? Al comstackr ese código en C, sin un molde obtendrás una advertencia del comstackdor (o error si se comstack con -Werror ). Al comstackr en C ++, tendrá un error simple porque la firma del parámetro no coincide. ¿Pero por qué?

Porque estos no tienen equivalencia directa:

 const char **p; // ptr-to-ptr-to-const-char // p and *p are mutable **p is const char **p; // ptr-to-ptr-to-char // p, *p, and **p are all mutable 

Al comstackr con clang , la advertencia exacta en C se da como:

main.c: 15: 9: Pasar char ** al parámetro de tipo const char ** descarta calificadores en tipos de punteros nesteds.

VS2010 y VS2012 ambos, por otro lado, lanzan un error:

error C2440: ‘inicialización’: no ​​se puede convertir de ‘char **’ a ‘const char **’

Parece extraño, pero VS es realmente más correcto (las maravillas nunca cesan).

Y eso tiene perfecto sentido. Ubicado en la statement de tipo es el hecho de que el primero de ellos no permite la modificación de los datos finales, el segundo sí lo hace . Desde arriba, sabemos que char ** y const char ** (también conocido como char const ** ) no son lo mismo. En la parte inferior de uno se encuentra un puntero a un const char , mientras que el otro tiene un puntero a char .

editar : incluso respondí la pregunta incorrecta. Mi respuesta es completamente irrelevante! Ignorame por favor

edición 2 : después de que el preguntador caballero clarifica su pregunta, resulta que mi respuesta es de hecho relevante. Así es la vida.

Este es un poco divertido de C, lo cual tiene sentido si piensas lo suficiente al respecto.

Básicamente, la conversión:

 char** ptr; const char** const_ptr; const_ptr = ptr; // < -- BAD! 

No se permite.

¿Por qué? ¿Podrías preguntar? "¡Estoy haciendo las cosas más complicadas! Esto obviamente es algo bueno".


Bueno, piensa en esto. Si eso estuviera permitido, entonces:

 const char c = 'A'; char* ptr; const char** const_ptr = &ptr; // < -- ILLEGAL, but what if this were legal? *const_ptr = &c; *ptr = 'B'; // <- you just assigned to "const char c" above. 

BAM estás muerto. Entonces ... no 🙂

La advertencia le dice que la función que está llamando espera que el parámetro dado sea const char** pero está pasando un parámetro char** . Para deshacerte de esta advertencia, podrías

  • realmente pasar en un const char**
  • envía tu parámetro a un const char** (como lo estás haciendo actualmente)
  • cambie el prototipo de función para que la función espere un char**