resueltos - ¿Están los literales de las cuerdas const?
estadistica elemental solucionario (8)
La respuesta de Johannes es correcta con respecto al tipo y contenido. Pero además de eso, sí, es un comportamiento indefinido modificar los contenidos de un literal de cadena.
Con respecto a tu pregunta sobre argv
:
Los parámetros argc y argv y las cadenas apuntadas por la matriz argv serán modificables por el programa y conservarán sus últimos valores almacenados entre el inicio del programa y la terminación del programa.
Tanto GCC como Clang no se quejan si asigno un literal de cadena a un char*
, incluso cuando se usan muchas opciones pedantes ( -Wall -W -pedantic -std=c99
):
char *foo = "bar";
mientras ellos (por supuesto) se quejan si asigno un const char*
a un char*
.
¿Esto significa que los literales de cadena se consideran de tipo char*
? ¿No deberían ser const char*
? ¡No se trata de un comportamiento definido si se modifican!
Y (una pregunta no correlacionada) ¿qué pasa con los parámetros de línea de comandos (es decir: argv
): se considera que es una matriz de literales de cadena?
Los literales de cadena tienen el tipo formal char []
pero el tipo semántico es const char []
. Los puristas lo odian, pero esto generalmente es útil e inofensivo, a excepción de traer muchos novatos al SO con "¿POR QUÉ ESTÁ CRUZANDO MI PROGRAMA?!?!" preguntas
Son const char *, pero hay una exclusión específica para asignarlos a char * para código heredado que existía antes de const did. Y los argumentos de línea de comandos definitivamente no son literales, se crean en tiempo de ejecución.
Son del tipo char[N]
donde N
es el número de caracteres, incluido el /0
finalizador. Entonces sí, puedes asignarlos a char*
, pero aún no puedes escribirles (el efecto no estará definido).
Wrt argv
: apunta a una serie de punteros a cadenas. Esas cadenas son explícitamente modificables. Puede cambiarlos y se requiere que contengan el último valor almacenado.
Tanto en C89 como en C99, los literales de cadena son de tipo char *
(por razones históricas, según lo entiendo). Estás en lo correcto al tratar de modificar uno de los resultados en un comportamiento indefinido. GCC tiene una bandera de advertencia específica, -Wwrite-strings (que no es parte de -Wall
), que te avisará si intentas hacerlo.
En cuanto a argv
, los argumentos se copian en el espacio de direcciones de su programa, y se pueden modificar de forma segura en su función main()
.
EDITAR : Whoops, tenía -Wno-write-strings
copiados por accidente. Actualizado con la forma correcta (positiva) de la bandera de advertencia.
(Lo siento, acabo de notar que esta pregunta está etiquetada como c
, no c++
. ¡Tal vez mi respuesta no es tan relevante para esta pregunta después de todo!)
Los literales de cadena no son const
o not-const
, existe una regla extraña especial para los literales.
( Resumen : Literales se pueden tomar por referencia-a-matriz como foo( const char (&)[N])
y no se pueden tomar como la matriz no const. Ellos prefieren decaer a const char *
. Hasta ahora, eso hace parece que son const
. Pero existe una regla de legado especial que permite que los literales se descompongan en char *
. Consulte los experimentos a continuación).
(Después de los experimentos realizados en clang3.3 con -std=gnu++0x
. ¿Quizás esto sea un problema de C ++ 11? ¿O específico para clang? De cualquier forma, hay algo extraño en curso).
Al principio, los literales parecen ser const
:
void foo( const char * ) { std::cout << "const char *" << std::endl; }
void foo( char * ) { std::cout << " char *" << std::endl; }
int main() {
const char arr_cc[3] = "hi";
char arr_c[3] = "hi";
foo(arr_cc); // const char *
foo(arr_c); // char *
foo("hi"); // const char *
}
Las dos matrices se comportan como se espera, lo que demuestra que foo
puede decirnos si el puntero es const
o no. Luego "hi"
selecciona la versión const
de foo
. Entonces parece que eso lo soluciona: los literales son const
... ¿no?
Pero , si elimina void foo( const char * )
entonces se vuelve extraño. Primero, la llamada a foo(arr_c)
falla con un error en tiempo de compilación. Eso es lo que se espera Pero la llamada literal ( foo("hi")
) funciona a través de la llamada non-const.
Entonces, los literales son "más const" que arr_c
(porque prefieren decaer al const char *
, a diferencia de arr_c
. Pero los literales son "menos const" que arr_cc
porque están dispuestos a decaer a char *
si es necesario.
(Clang da una advertencia cuando se descompone en char *
).
Pero, ¿y el decaimiento? Evitemos esto por simplicidad.
Tomemos las matrices por referencia en foo en su lugar. Esto nos da resultados más ''intuitivos'':
void foo( const char (&)[3] ) { std::cout << "const char (&)[3]" << std::endl; }
void foo( char (&)[3] ) { std::cout << " char (&)[3]" << std::endl; }
Como antes, el literal y la matriz const ( arr_cc
) utilizan la versión const, y la versión no const es utilizada por arr_c
. Y si eliminamos foo( const char (&)[3] )
, entonces obtenemos errores con ambos foo(arr_cc);
y foo("hi");
. En resumen, si evitamos el puntero-decaimiento y usamos reference-to-array, los literales se comportan como si fueran const
.
¿Plantillas?
En las plantillas, el sistema deducirá const char *
lugar de char *
y usted quedará "atrapado" con eso.
template<typename T>
void bar(T *t) { // will deduce const char when a literal is supplied
foo(t);
}
Así que, básicamente, un literal se comporta como const
en todo momento, excepto en el caso particular en que se inicializa directamente un char *
con un literal.
Para mayor información, el borrador del estándar C99 ( C89 y C11 tienen una redacción similar ) en la sección 6.4.5
cadenas, el párrafo 5 dice:
[...] un byte o código de valor cero se agrega a cada secuencia de caracteres multibyte que resulta de una cadena literal o literales. La secuencia de caracteres multibyte se usa luego para inicializar una matriz de duración de almacenamiento estático y longitud suficiente para contener la secuencia. Para los literales de cadena de caracteres, los elementos de la matriz tienen tipo char , y se inicializan con los bytes individuales de la secuencia de caracteres multibyte; [...]
Así que esto dice que un literal de cadena tiene una duración de almacenamiento estática ( dura el tiempo de vida del programa ) y su tipo es char[]
(no char *
) y su longitud es el tamaño del literal de cadena con un cero adjunto. * El párrafo 6` dice:
Si el programa intenta modificar dicha matriz, el comportamiento no está definido.
Por lo tanto, intentar modificar una cadena literal es un comportamiento indefinido independientemente del hecho de que no sean const
.
Con respecto a argv
en la sección 5.1.2.2.1
el párrafo 2 del inicio del programa dice:
Si se declaran, los parámetros de la función principal obedecerán las siguientes restricciones:
[...]
-Los parámetros argc y argv y las cadenas apuntadas por la matriz argv serán modificables por el programa y conservarán sus últimos valores almacenados entre el inicio del programa y la terminación del programa.
Entonces argv
no se considera una matriz de literales de cadenas y está bien modificar los contenidos de argv
.
Usando la opción -Wwrite-strings
obtendrás:
warning: initialization discards qualifiers from pointer target type
Independientemente de esa opción, GCC colocará los literales en la sección de memoria de solo lectura, a menos que se indique lo contrario utilizando -fwritable-strings
(sin embargo, esta opción se ha eliminado de las versiones recientes de GCC).
Los parámetros de línea de comando no son const, por lo general viven en la pila.