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
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 desomething_else
es mayor quesizeof(buf)
. Por lo general, configuro el último carácterbuf[sizeof(buf)-1] = 0
para protegerme de eso, o sibuf
tiene una inicialización cero, usesizeof(buf) - 1
como la longitud de la copia. - Use
strlcpy
ostrcpy_s
o inclusosnprintf
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
ysnprintf
no son directamente accesibles en MSVC, al menos los pedidos ystrcpy_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 destrlen
cuando ‘ esté preocupado por la memoria. De manera similar, puede realizar una verificación antes de la llamada astrcpy
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 achar*
, 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. Inclusostring[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? Unchar *
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 unchar[]
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 dechar* 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 sonconst
, 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 comoconst char *s = "literal";
- » Y en general » asdf » es una constante, por lo que debe declararse como constante. » : el mismo razonamiento requeriría un
const
enint n = 42;
, porque42
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 que1 + 1
evalúa como2
. Si el programa que vinculé arriba hace algo más que imprimirEFGH
, 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).
strncpy
rara vez es la respuesta correctachar[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 deseprate
aseparate
se romperá si el tamaño no se actualiza.strlen()
no incluye el carácter nulo, usandoMAX_MONTH_LENGTH
para mantener el tamaño máximo necesario parachar string[]
a menudo se ve mal. En mi opinión,MAX_MONTH_SIZE
sería mejor aquí.