¿Inicializar un char [] con una cadena literal es una mala práctica?

44

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

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

derecho. el tamaño debe ser la longitud más 1 sí?

esta es la salida

the size of september is 8 and the length is 9

El tamaño debe ser 10 seguramente. es como calcular el tamaño de la cadena antes de que se cambie por strcpy 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 un literal de cadena. Así que siempre haz uno de los siguientes:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");
Cole Johnson
fuente
Tenga en cuenta el "const" en la primera línea. ¿Podría ser que el autor asumió c ++ en lugar de c? En c ++ es "mala práctica", porque un literal debe ser constante y cualquier compilador reciente de c ++ dará una advertencia (o error) sobre la asignación de un literal constante a una matriz no constante.
André
@ André C ++ define literales de cadena como matrices constantes, porque esa es la única forma segura de tratar con ellos. Que C no sea ​​el problema, así que tienes una regla social que hace cumplir lo seguro
Caleth
@Caleth. Lo sé, intentaba más argumentar que el autor de la respuesta se acercaba a la "mala práctica" desde una perspectiva de c ++.
André
@ André no es una mala práctica en C ++, porque no es una práctica , es un error de tipo directo. Que debe ser un error de tipo en C, pero no lo es, lo que tiene que tener una regla guía de estilo que le dice "está prohibido"
Caleth

Respuestas:

59

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

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

En C (y ha etiquetado esto como C), esa es prácticamente la única forma de inicializar una matriz charcon un valor de cadena (la inicialización es diferente de la asignación). Puedes escribir cualquiera

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 charcon un byte final de 0, por lo que el tamaño de la matriz es 8 ('o', 'c', 't', 'o', 'b', 'e', ​​'r', 0). En los segundos dos casos, el tamaño de la matriz se especifica como parte de la declaración (8 y MAX_MONTH_LENGTH, lo que sea que sea).

Lo que no puedes hacer es escribir algo como

char string[];
string = "october";

o

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

En el primer caso, la declaración de stringestá incompleta porque no se ha especificado un tamaño de matriz y no hay inicializador para tomar el tamaño. En ambos casos, =no funcionará porque a) una expresión de matriz tal como stringpuede no ser el objetivo de una asignación yb) el =operador no está definido para copiar el contenido de una matriz a otra de todos modos.

Por esa misma razón, no puedes escribir

char string[] = foo;

donde fooes otra matriz de char. Esta forma de inicialización solo funcionará con literales de cadena.

EDITAR

Debo enmendar esto para decir que también puede inicializar matrices para contener una cadena con un inicializador de estilo de 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 a la vista usar literales de cadena.

EDITAR 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!
John Bode
fuente
@KeithThompson: no estoy en desacuerdo, solo lo agregué por completo.
John Bode
16
Tenga en cuenta que char[8] str = "october";es una mala práctica. Tuve que literalmente contarme a mí mismo para asegurarme de que no se tratara de un desbordamiento y se rompa bajo mantenimiento ... por ejemplo, corregir un error ortográfico de sepratea separatese romperá si el tamaño no se actualiza.
djechlin
1
Estoy de acuerdo con djechlin, es una mala práctica por las razones expuestas. La respuesta de JohnBode no comenta en absoluto el aspecto de "mala práctica" (que es la parte principal de la pregunta !!), solo explica lo que puede o no puede hacer para inicializar la matriz.
mastov
Menor: Como 'valor de longitud" regresó de strlen()no incluir el carácter nulo, utilizando MAX_MONTH_LENGTHpara mantener el tamaño máximo necesario para char string[]menudo se ve . OMI mal, MAX_MONTH_SIZEsería mejor aquí.
Chux - Restablecer Mónica
10

El único problema que recuerdo es asignar literal de cadena 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) se bloquea al intentar 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 para que esto no funcione:

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

Pero esto lo hará

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

Como último comentario, no usaría strcpyen 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 una llamada segura, strncpyes mucho más seguro:

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';
Maciej Piechotka
fuente
Todavía existe el riesgo de que el búfer se desborde strncpyporque no termina nulo la cadena copiada cuando la longitud something_elsees mayor que sizeof(buf). Por lo general, configuro el último carácter buf[sizeof(buf)-1] = 0para protegerlo, o si bufestá inicializado en cero, utilizo sizeof(buf) - 1como la longitud de la copia.
syockit
Use strlcpyo strcpy_so incluso snprintfsi tiene que hacerlo.
user253751
Fijo. Desafortunadamente, no hay una manera fácil de hacer esto a menos que tenga el lujo de trabajar con los compiladores más nuevos ( strlcpyy snprintfno se puede acceder directamente en MSVC, al menos pedidos y strcpy_sno en * nix).
Maciej Piechotka
@MaciejPiechotka: Bueno, gracias a Dios Unix rechazó el anexo k patrocinado por Microsoft.
Deduplicador
6

Una cosa que ninguno de los hilos menciona es esto:

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 la memoria. El estándar C insiste en que si se inicializa alguna parte de una matriz, todo lo es. Entonces, en este caso, es mejor hacerlo usted mismo. Creo que eso fue a lo que se refería Treuss.

Sin lugar a duda

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

es mejor que cualquiera:

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 generar un error de división de tiempo de compilación por cero si está a punto de desbordar la matriz.

Richard Fife
fuente
5

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

El código de muestra del enlace:

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

stringse asigna en la pila con 7 u 8 caracteres de longitud. No puedo recordar si está terminada en nulo de esta manera o no: el hilo al que se vinculó indicó que sí.

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

Otro desafío surge si pasa stringa otra función para que la otra función pueda escribir en la matriz. Debe decirle a la otra función cuánto dura la matriz para que no cree un desbordamiento. Podría pasar stringjunto con el resultado de, strlen()pero el hilo explica cómo esto puede explotar si stringno se termina con nulo.

Es 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é char[]es generalmente demasiado pequeño para los otros valores que necesito colocar allí. El uso de una constante definida ayuda a evitar ese problema.


sizeof stringle dará el tamaño del búfer (8 bytes); use el resultado de esa expresión en lugar de strlencuando le preocupa la memoria.
Del mismo modo, se puede realizar una prueba antes de la llamada a strcpypara ver si su memoria intermedia de destino es lo suficientemente grande para la cadena de origen: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Sí, si usted tiene que pasar la matriz a una función, tendrá que pasar su tamaño físico, así: foo (array, sizeof array / sizeof *array);. - John Bode

Comunidad
fuente
2
sizeof stringle dará el tamaño del búfer (8 bytes); use el resultado de esa expresión en lugar de strlencuando le preocupa la memoria. Del mismo modo, se puede realizar una prueba antes de la llamada a strcpypara ver si su memoria intermedia de destino es lo suficientemente grande para la cadena de origen: if (sizeof target > strlen(src)) { strcpy (target, src); }. Sí, si usted tiene que pasar la matriz a una función, tendrá que pasar su tamaño físico, así: foo (array, sizeof array / sizeof *array);.
John Bode
1
@ JohnBode: gracias, y esos son buenos puntos. He incorporado tu comentario en mi respuesta.
1
Más precisamente, la mayoría de las referencias al nombre de la matriz stringresultan en 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 de función es solo uno de los muchos contextos en los que esto sucede. char *ptr = string;es otro. Even string[0]es un ejemplo de esto; el []operador trabaja en punteros, no directamente en matrices. Lectura recomendada: Sección 6 de la FAQ comp.lang.c .
Keith Thompson el
¡Finalmente una respuesta que realmente se refiere a la pregunta!
mastov
2

Creo que la idea de "mala práctica" proviene del hecho de que esta forma:

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

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

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

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

o directamente:

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

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

toto
fuente
¿No haría solo una copia si intenta modificarla? Creo que el compilador sería más inteligente que eso
Cole Johnson, el
1
¿Qué pasa con casos como char time_buf[] = "00:00";donde vas 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 de almacenamiento del literal de cadena es desconocido (implementación definida), mientras que modificar los bytes de a char[]es perfectamente legal porque la inicialización copia los bytes en un espacio grabable asignado en la pila. Decir que es "menos eficiente" o "mala práctica" sin dar detalles sobre los matices char* vs char[]es engañoso.
Braden Best
-3

Nunca es mucho tiempo, pero debe evitar la inicialización char [] a string, porque "string" 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 el commend, mezclé un poco char [] con char *, eso no es bueno ya que difieren un poco.

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

Dainius
fuente
3
Incorrecto. La inicialización copia el contenido del literal de cadena en la matriz. El objeto de matriz no lo es a constmenos que lo defina de esa manera. (Y los literales de cadena en C no lo 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á hablando; está mejor escrito comoconst char *s = "literal";
Keith Thompson el
de hecho mi culpa, mezclé char [] con char *. Pero no estaría tan seguro de copiar contenido en una matriz. La comprobación rápida con el compilador MS C muestra que 'char c [] = "asdf";' creará 'cadena' en el segmento constante y luego asignará esta dirección a la variable de matriz. Esa es en realidad una razón por la que dije sobre evitar asignaciones a una matriz de caracteres no constante.
Dainius
Soy escéptico Pruebe este programa y hágame saber qué resultado obtiene.
Keith Thompson
2
"Y en general" asdf "es una constante, por lo que debe declararse como constante". - El mismo razonamiento requeriría un constencendido int n = 42;, porque 42es una constante.
Keith Thompson el
1
No importa en qué máquina estés. El estándar de idioma garantiza que ces modificable. Es exactamente una garantía tan fuerte como la que 1 + 1evalúa 2. Si el programa al que me vinculé anteriormente hace algo más que imprimir EFGH, indica una implementación de C no conforme.
Keith Thompson el