c - songs - tag de las 20 preguntas
poniendo a cero la memoria (12)
Cuando defina el char buffer[1024]
sin inicializar, obtendrá datos no definidos en él. Por ejemplo, Visual C ++ en modo de depuración se inicializará con 0xcd. En el modo Liberación, simplemente asignará la memoria y no importará lo que suceda en ese bloque desde el uso anterior.
Además, sus ejemplos demuestran el tiempo de ejecución frente a la inicialización del tiempo de compilación. Si su char buffer[1024] = { 0 }
es una declaración global o estática, se almacenará en el segmento de datos del binario con sus datos inicializados, lo que aumentará su tamaño binario en aproximadamente 1024 bytes (en este caso). Si la definición está en una función, se almacena en la pila y se asigna en tiempo de ejecución y no se almacena en el binario. Si proporciona un inicializador en este caso, el inicializador se almacena en el binario y se realiza un equivalente de memcpy()
para inicializar el buffer
en tiempo de ejecución.
Con suerte, esto te ayudará a decidir qué método funciona mejor para ti.
gcc 4.4.4 C89
Me pregunto qué hacen la mayoría de los programadores de C cuando quieren poner a cero la memoria.
Por ejemplo, tengo un búfer de 1024 bytes. A veces hago esto:
char buffer[1024] = {0};
Lo que pondrá a cero todos los bytes.
Sin embargo, ¿debería declararlo así y usar memset?
char buffer[1024];
.
.
memset(buffer, 0, sizeof(buffer));
¿Hay alguna razón real para poner a cero la memoria? ¿Qué es lo peor que puede pasar al no hacerlo?
Depende de cómo lo llene: si planea escribirlo antes incluso de leer algo, ¿por qué molestarse? También depende de para qué va a utilizar el búfer: si va a tratarse como una cadena, entonces solo necesita establecer el primer byte en /0
:
char buffer[1024];
buffer[0] = ''/0'';
Sin embargo, si lo está utilizando como un flujo de bytes, es probable que el contenido de la matriz completa sea relevante, por lo que la memset
completa o la configuración en { 0 }
como en su ejemplo es un movimiento inteligente.
En este caso particular, no hay mucha diferencia. Prefiero = { 0 }
sobre memset
porque memset
es más propenso a errores:
- Brinda la oportunidad de equivocarse en los límites.
-
memset
oportunidad de mezclar los argumentos conmemset
(por ejemplo,memset(buf, sizeof buf, 0)
lugar dememset(buf, 0, sizeof buf)
.
En general, = { 0 }
es mejor para inicializar struct
también. Inicializa efectivamente a todos los miembros como si hubiera escrito = 0
para inicializar cada uno. Esto significa que se garantiza que los miembros del puntero se inicialicen con el puntero nulo ( que podría no ser todos los bits-cero , y todos los bits-cero es lo que obtendría si hubiera usado memset
).
Por otro lado, = { 0 }
puede dejar los bits de relleno en una struct
como basura, por lo que podría no ser apropiado si planea usar memcmp
para compararlos más adelante.
Esta publicación ha sido editada en gran medida para que sea correcta. Muchas gracias a Tyler McHenery por señalar lo que me perdí.
char buffer[1024] = {0};
Establecerá el primer carácter en el búfer en nulo y el compilador expandirá todos los caracteres no inicializados a 0 también. En tal caso, parece que las diferencias entre las dos técnicas se reducen a si el compilador genera un código más optimizado para la inicialización de la matriz o si memset se optimiza más rápido que el código compilado generado.
Anteriormente dije:
búfer de caracteres [1024] = {0};
Establecerá el primer char en el búfer en nulo. Esa técnica se usa comúnmente para cadenas terminadas en nulo, ya que todas las informaciones posteriores al primer nulo son ignoradas por las funciones subsiguientes (sin buggy) que manejan cadenas terminadas en nulo.
Lo cual no es del todo cierto. Perdón por la falta de comunicación, y gracias de nuevo por las correcciones.
Lo peor que puede pasar si no lo hace es que escribe algunos datos carácter por carácter y luego lo interpreta como una cadena (y no escribió un terminador nulo). O terminas sin darte cuenta de que una sección no estaba inicializada y la leías como si fuera información válida. Básicamente: todo tipo de maldad.
Memset debería estar bien (siempre que corrija el tamaño de error tipográfico :-)). Prefiero eso a tu primer ejemplo porque creo que está más claro.
Para la memoria asignada dinámicamente, uso calloc en lugar de malloc y memset.
Lo peor que puede pasar? Usted termina (sin saberlo) con una cadena que no termina en NULL, o un número entero que hereda lo que sucedió a la derecha después de que imprimió en parte del búfer. Sin embargo, las cadenas no terminadas también pueden suceder de otras formas, incluso si inicializa el búfer.
Editar (de los comentarios) El fin del mundo también es una posibilidad remota, dependiendo de lo que esté haciendo.
Cualquiera de las dos es indeseable. Sin embargo, a menos que se evite por completo la memoria asignada dinámicamente, la mayoría de los buffers asignados estáticamente son bastante pequeños, lo que hace que memset()
relativamente barato. De hecho, mucho más barato que la mayoría de las llamadas a calloc()
para bloques dinámicos, que tienden a ser más grandes que ~ 2k.
c99 contiene lenguaje con respecto a los valores de inicialización predeterminados, sin embargo, no puedo hacer que gcc -std=c99
acuerdo con eso, utilizando cualquier tipo de almacenamiento.
Aún así, con muchos compiladores antiguos (y compiladores que no son del todo c99) todavía en uso, prefiero usar memset()
No estoy familiarizado con el
char buffer[1024] = {0};
técnica. Pero suponiendo que haga lo que creo que hace, hay una diferencia (potencial) en las dos técnicas.
El primero se realiza en tiempo de compilación, y el búfer será parte de la imagen estática del ejecutable, y por lo tanto será 0 cuando cargue.
Esto último se hará en tiempo de ejecución.
El primero puede incurrir en algún comportamiento de tiempo de carga. Si solo tienes:
char buffer[1024];
los cargadores modernos bien pueden "virtualmente" cargar eso ... es decir, no tomará ningún espacio real en el archivo, simplemente será una instrucción para que el cargador genere un bloque cuando se carga el programa. No estoy lo suficientemente cómodo con los cargadores modernos dicen si eso es cierto o no.
Pero si lo inicializa previamente, entonces será necesario cargarlo desde el ejecutable.
Cuidado, ninguno de estos tiene impactos "reales" en el rendimiento en lo pequeño. Es posible que no tengan ninguna en el "grande". Solo digo que hay potencial aquí, y las dos técnicas de hecho están haciendo algo muy diferente.
Prefiero usar memset
para borrar un trozo de memoria, especialmente cuando se trabaja con cadenas. Quiero saber sin lugar a dudas que habrá un delimitador nulo después de mi cadena. Sí, sé que puede agregar un /0
al final de cada cadena y algunas funciones lo hacen por usted, pero no quiero dudas de que esto ha tenido lugar.
Una función podría fallar al usar su búfer, y el búfer permanece sin cambios. ¿Prefieres tener un búfer de basura desconocida, o nada?
Sí, el método calloc () definido en stdlib.h asigna memoria inicializada con ceros.
También uso memset (buffer, 0, sizeof (buffer));
El riesgo de no usarlo es que no hay garantía de que el búfer que está utilizando esté completamente vacío, podría haber basura que podría llevar a un comportamiento impredecible.
Siempre unir a 0 después de malloc, es una muy buena práctica.
Una de las cosas que pueden suceder si no se inicializa es que corre el riesgo de filtrar información confidencial.
La memoria no inicializada puede tener algo sensible en un uso anterior de esa memoria. Tal vez una contraseña o clave de cifrado o parte de un correo electrónico privado. Más tarde, su código puede transmitir ese búfer o estructura en algún lugar, o escribirlo en el disco, y si solo lo llenó parcialmente, el resto aún contiene los contenidos anteriores. Ciertos sistemas seguros requieren buffers de zeroizing cuando un espacio de direcciones puede contener información confidencial.
Yo prefiero enormemente
char buffer[1024] = { 0 };
Es más corto, más fácil de leer y menos propenso a errores. Solo use memset
en buffers asignados dinámicamente, y luego prefiera calloc
.