¿Es una mala práctica inicializar un char [] con una cadena literal?

Estaba leyendo un hilo titulado «strlen vs sizeof» en CodeGuru , y una de las respuestas indica que «de todos modos» es [sic] una mala práctica inicializar [sic] una matriz char con una cadena literal «.

¿Es esto cierto o es solo su opinión (aunque sea un» miembro de élite «)?


Aquí está la pregunta original:

#include <stdio.h> #include<string.h> main() { char string[] = "october"; strcpy(string, "september"); printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string)); return 0; } 

correcto. El tamaño debe ser la longitud más 1 ¿sí?

esto es la salida

the size of september is 8 and the length is 9

el tamaño debe ser seguramente 10. Es como calcular el tamaño de la cadena antes de que strcpy la cambie, pero la longitud después.

¿Hay algún problema con mi sintaxis o qué?


Aquí está la respuesta :

De todos modos, es una mala práctica inicializar una matriz de caracteres con una cadena literal. Por lo tanto, siempre realice una de las siguientes acciones:

const char string1[] = "october"; char string2[20]; strcpy(string2, "september"); 

Comentarios

  • Tenga en cuenta la » const » en la primera línea. ¿Podría ser que el autor asumiera c ++ en lugar de c? En c ++ es » una mala práctica «, porque un literal debe ser constante y cualquier compilador reciente de c ++ dará una advertencia (o error) acerca de la asignación de un literal const a una matriz no constante.
  • @Andr é C ++ define las cadenas literales como matrices constantes, porque esa es la única forma segura de tratar con ellos. Ese C no ‘ t es el problema, así que tienes una regla social que refuerza lo seguro
  • @Caleth. Lo sé, estaba más tratando de argumentar que el autor de la respuesta se estaba acercando a la » mala práctica » desde una perspectiva de C ++.
  • @Andr é no es ‘ una mala práctica en C ++, porque no es ‘ una práctica , es ‘ un error de tipo directo. Debería ser un error de tipo en C, pero no es ‘ t, por lo que debe tener una regla de guía de estilo que le indique » Está ‘ s prohibido »

Responder

De todos modos, es una mala práctica inicializar una matriz de caracteres con una cadena literal.

El autor de ese comentario nunca lo justifica realmente, y encuentro la declaración desconcertante.

En C (y «has etiquetado esto como C), eso» Es prácticamente la única forma de inicializar una matriz de char con un valor de cadena (la inicialización es diferente de la asignación). Puede escribir

char string[] = "october"; 

o

char string[8] = "october"; 

o

char string[MAX_MONTH_LENGTH] = "october"; 

En el primer caso, el tamaño de la matriz se toma del tamaño del inicializador. Los literales de cadena se almacenan como matrices de char con un byte de terminación 0, por lo que el tamaño de la matriz es 8 («o», «c», «t», «o», «b», «e», «r», 0). En los dos segundos casos, el tamaño de la matriz se especifica como parte de la declaración (8 y MAX_MONTH_LENGTH, sea lo que sea).

Lo que no puede hacer es escribir algo como

char string[]; string = "october"; 

o

char string[8]; string = "october"; 

etc. En el primer caso, la declaración de string está incompleta porque no se ha especificado un tamaño de matriz y no hay un inicializador del que tomar el tamaño. En ambos En algunos casos, = no funcionará porque a) una expresión de matriz como string puede no ser el objetivo de una asignación yb) el operador = no está «definido para copiar el contenido de un arreglo a otro de todos modos.

Por ese mismo símbolo, no puede» escribir

char string[] = foo; 

donde foo es otra matriz de char. Esta forma de inicialización solo funcionará con cadenas literales.

EDIT

Debo modificar esto para decir que también puede inicializar matrices para contener una cadena con un inicializador de estilo matriz, como

char string[] = {"o", "c", "t", "o", "b", "e", "r", 0}; 

o

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII 

pero es más fácil para los ojos usar cadenas literales.

EDIT 2

Para asignar el contenido de una matriz fuera de una declaración, necesitaría usar strcpy/strncpy (para cadenas terminadas en 0) o memcpy (para cualquier otro tipo de matriz):

if (sizeof string > strlen("october")) strcpy(string, "october"); 

o

strncpy(string, "october", sizeof string); // only copies as many characters as will // fit in the target buffer; 0 terminator // may not be copied, but the buffer is // uselessly completely zeroed if the // string is shorter! 

Comentarios

  • strncpy rara vez es la respuesta correcta
  • @KeithThompson: no estoy en desacuerdo, solo lo agregué para completar ‘ por favor.
  • Tenga en cuenta que char[8] str = "october"; es una mala práctica. Tuve que contar literalmente char para asegurarme de que no era ‘ t un desbordamiento y se rompe durante el mantenimiento … p. Ej. corregir un error ortográfico de seprate a separate se romperá si el tamaño no se actualiza.
  • Estoy de acuerdo con djechlin, es una mala práctica por las razones dadas. JohnBode ‘ s respuesta no ‘ no comenta en absoluto sobre la » mala práctica » aspecto (que es la parte principal de la pregunta), solo explica lo que puede o no puede hacer para inicializar la matriz.
  • Menor: como ‘ longitud » El valor devuelto por strlen() no incluye el carácter nulo, usando MAX_MONTH_LENGTH para mantener el tamaño máximo necesario para char string[] a menudo se ve mal. En mi opinión, MAX_MONTH_SIZE sería mejor aquí.

Responder

El único problema que recuerdo es asignar una cadena literal a char *:

char var1[] = "september"; var1[0] = "S"; // Ok - 10 element char array allocated on stack char const *var2 = "september"; var2[0] = "S"; // Compile time error - pointer to constant string char *var3 = "september"; var3[0] = "S"; // Modifying some memory - which may result in modifying... something or crash 

Por ejemplo, tome este programa:

#include <stdio.h> int main() { char *var1 = "september"; char *var2 = "september"; var1[0] = "S"; printf("%s\n", var2); } 

Esto en mi plataforma (Linux) falla cuando intenta escribir en la página marcada como de solo lectura. En otras plataformas, podría imprimir «Septiembre», etc.

Dicho esto, la inicialización por literal hace la cantidad específica de reserva, por lo que esto no funcionará:

char buf[] = "May"; strncpy(buf, "September", sizeof(buf)); // Result "Sep" 

Pero esto

char buf[32] = "May"; strncpy(buf, "September", sizeof(buf)); 

Como último comentario, no «usaría strcpy en absoluto:

char buf[8]; strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory 

Si bien algunos compiladores pueden cambiarlo a seguro, la llamada strncpy es mucho más segura:

char buf[1024]; strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers. buf[sizeof(buf) - 1] = "\0"; 

Comentarios

  • Aún hay ‘ s riesgo de saturación del búfer en esa strncpy porque no ‘ t nula termina la cadena copiada cuando la longitud de something_else es mayor que sizeof(buf). Por lo general, configuro el último carácter buf[sizeof(buf)-1] = 0 para protegerme de eso, o si buf tiene una inicialización cero, use sizeof(buf) - 1 como la longitud de la copia.
  • Use strlcpy o strcpy_s o incluso snprintf si es necesario.
  • Fijo. Lamentablemente, no existe una manera fácil de hacer esto a menos que tenga el lujo de trabajar con los compiladores más nuevos (strlcpy y snprintf no son directamente accesibles en MSVC, al menos los pedidos y strcpy_s no están en * nix).
  • @MaciejPiechotka: Bueno, gracias a Dios, Unix rechazó el anexo k patrocinado por microsoft.

Responder

Principalmente porque no tendrá el tamaño del char[] en una variable / construcción que puede usar fácilmente dentro del programa.

El ejemplo de código del enlace:

 char string[] = "october"; strcpy(string, "september"); 

string está asignado en la pila con una longitud de 7 u 8 caracteres. No puedo recordar si tiene una terminación nula de esta manera o no: el hilo al que vinculó indicó que es .

Copiar «septiembre» sobre esa cadena es un desbordamiento de memoria obvio.

Otro desafío surge si pasa string a otra funciónpara que la otra función pueda escribir en la matriz. Debe decirle a la otra función cuánto tiempo tiene la matriz para que no genere un desbordamiento. Puede pasar string junto con el resultado de strlen() pero el hilo explica cómo esto puede explotar si string no tiene terminación nula.

Estás mejor asignar una cadena con un tamaño fijo (preferiblemente definido como una constante) y luego pasar la matriz y el tamaño fijo a la otra función. Los comentarios de @John Bode son correctos, y hay formas de mitigar estos riesgos. También requieren más esfuerzo de su parte para usarlos.

En mi experiencia, el valor que inicialicé el char[] to suele ser demasiado pequeño para los otros valores que necesito colocar allí. Usar una constante definida ayuda a evitar ese problema.


sizeof string le dará el tamaño del búfer (8 bytes); use el resultado de esa expresión en lugar de strlen cuando esté preocupado por la memoria.
De manera similar, puede hacer una verificación antes de la llamada a strcpy para ver si su búfer de destino es lo suficientemente grande para la cadena de origen: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Sí, si tiene que pasar la matriz a una función, » también debe pasar su tamaño físico: foo (array, sizeof array / sizeof *array);. – John Bode

Comentarios

  • sizeof string le dará el tamaño del búfer (8 bytes); use el resultado de esa expresión en lugar de strlen cuando ‘ esté preocupado por la memoria. De manera similar, puede realizar una verificación antes de la llamada a strcpy para ver si su búfer de destino es lo suficientemente grande para la cadena de origen: if (sizeof target > strlen(src)) { strcpy (target, src); }. Sí, si tiene que pasar la matriz a una función, ‘ también deberá pasar su tamaño físico: foo (array, sizeof array / sizeof *array);.
  • @JohnBode – gracias, y esos son buenos puntos. He incorporado su comentario en mi respuesta.
  • Más precisamente, la mayoría de las referencias al nombre de la matriz string dan como resultado una conversión implícita a char*, apuntando al primer elemento de la matriz. Esto pierde la información de los límites de la matriz. Una llamada a función es solo uno de los muchos contextos en los que esto sucede. char *ptr = string; es otro. Incluso string[0] es un ejemplo de esto; el operador [] trabaja en punteros, no directamente en matrices. Lectura sugerida: Sección 6 de las comp.lang.c FAQ .
  • ¡Finalmente una respuesta que realmente se refiere a la pregunta!

Respuesta

Una cosa que ninguno de los hilos menciona es esta:

char whopping_great[8192] = "foo"; 

vs.

char whopping_great[8192]; memcpy(whopping_great, "foo", sizeof("foo")); 

El primero hará algo como:

memcpy(whopping_great, "foo", sizeof("foo")); memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo")); 

Este último solo hace el memcpy. El estándar C insiste en que si se inicializa cualquier parte de una matriz, todo lo está. Entonces, en este caso, es mejor hacerlo usted mismo. Creo que eso puede haber sido a lo que se refería treuss.

Seguro

char whopping_big[8192]; whopping_big[0] = 0; 

es mejor que:

char whopping_big[8192] = {0}; 

o

char whopping_big[8192] = ""; 

ps Para puntos de bonificación, puede hacer:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo")); 

para lanzar un error dividido por cero en el tiempo de compilación si está a punto de desbordar la matriz.

Respuesta

Creo que la idea de «malas prácticas» proviene del hecho de que esta forma:

char string[] = "october is a nice month"; 

hace implícitamente un strcpy desde el código fuente de la máquina a la pila.

Es más eficiente manejar solo un enlace a esa cadena. Como con:

char *string = "october is a nice month"; 

o directamente:

strcpy(output, "october is a nice month"); 

(pero, por supuesto, en la mayoría código probablemente no importa)

Comentarios

  • No ‘ t solo haría una copia si intentas modificarlo, creo que el compilador sería más inteligente que eso
  • ¿Qué pasa con casos como char time_buf[] = "00:00"; donde ‘ ¿va a modificar un búfer? Un char * inicializado en un literal de cadena se establece en la dirección del primer byte, por lo que intentar modificarlo da como resultado un comportamiento indefinido porque el método del almacenamiento literal de cadena ‘ es desconocido (implementación definida), mientras que modificar los bytes de un char[] es perfectamente legal porque el La inicialización copia los bytes en un espacio de escritura asignado en la pila. Decir que ‘ es » menos eficiente o » mala práctica » sin profundizar en los matices de char* vs char[] es engañoso.

Answer

Nunca es realmente mucho tiempo, pero debes evitar la inicialización char [] a cadena, porque «cadena» es const char *, y lo está asignando a char *. Entonces, si pasa este char [] al método que cambia los datos, puede tener un comportamiento interesante.

Como dijo elogio, mezclé un poco char [] con char *, eso no es bueno ya que difieren un poco.

No hay nada malo en asignar datos a una matriz de caracteres, pero como la intención de usar esta matriz es usarla como «cadena» (char *), es fácil olvidar que no debe modificarla matriz.

Comentarios

  • Incorrecto. La inicialización copia el contenido del literal de cadena en la matriz. El objeto de matriz no es ‘ t const a menos que lo defina de esa manera.(Y los literales de cadena en C no son const, aunque cualquier intento de modificar un literal de cadena tiene un comportamiento indefinido). char *s = "literal"; tiene el tipo de comportamiento del que ‘ estás hablando; es ‘ mejor escrito como const char *s = "literal";
  • » Y en general » asdf » es una constante, por lo que debe declararse como constante. » : el mismo razonamiento requeriría un const en int n = 42;, porque 42 es una constante.
  • No ‘ importa en qué máquina ‘ se encuentre. El estándar de lenguaje garantiza que c es modificable. Es una garantía ‘ exactamente tan fuerte como la que 1 + 1 evalúa como 2. Si el programa que vinculé arriba hace algo más que imprimir EFGH, indica una implementación de C no conforme.
  • @Dainus: el compilador MSVC tiene una optimización llamada ‘ agrupación de cadenas ‘ que colocará una única copia de cadenas idénticas en un segmento de solo lectura si puede garantizar que los usos de ellas sean de solo lectura. Desactive la optimización para ver el comportamiento ‘ normal ‘. Para su información, » Editar y continuar » requiere que esta opción esté activada. Más información aquí: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
  • Creo que Dainius sugiere que en muchos En los casos, el error es que la variable en sí debe marcarse const char *const para evitar la modificación de los bytes o del puntero en sí, pero en muchos casos los programadores dejarán uno o ambos mutables, lo que permitirá que algún código de tiempo de ejecución modificar lo que parece ser una constante escrita (pero no es constante).

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *