El almacenamiento devuelto por malloc() es no inicializado en cero. Nunca asumas que lo es.
En su programa de prueba, es solo una casualidad:supongo que el malloc()
acabo de recibir un bloque nuevo de mmap()
, pero tampoco confíes en eso.
Por ejemplo, si ejecuto su programa en mi máquina de esta manera:
$ echo 'void __attribute__((constructor)) p(void){
void *b = malloc(4444); memset(b, 4, 4444); free(b);
}' | cc -include stdlib.h -include string.h -xc - -shared -o pollute.so
$ LD_PRELOAD=./pollute.so ./your_program
a at 0x7ffd40d3aa60: 1256994848 21891 1256994464 21891 1087613792 32765 0 0
b at 0x55834c75d010: 67372036 67372036 67372036 67372036 67372036 67372036 67372036 67372036
Su segundo ejemplo es simplemente exponer un artefacto del malloc
implementación en glibc; si haces eso repite malloc
/free
con un búfer de más de 8 bytes, verá claramente que solo los primeros 8 bytes se ponen a cero, como en el siguiente código de ejemplo.
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
const size_t n = 4;
const size_t m = 0x10;
int main()
{
for (size_t i = n; i; --i) {
int *const p = malloc(m*sizeof(int));
printf("%p ", p);
for (size_t j = 0; j < m; ++j) {
printf("%d:", p[j]);
++p[j];
printf("%d ", p[j]);
}
free(p);
printf("\n");
}
return 0;
}
Salida:
0x55be12864010 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1
0x55be12864010 0:1 0:1 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2
0x55be12864010 0:1 0:1 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3
0x55be12864010 0:1 0:1 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4
Independientemente de cómo se inicialice la pila, no está viendo una pila prístina, porque la biblioteca C hace una serie de cosas antes de llamar a main
y tocan la pila.
Con la biblioteca GNU C, en x86-64, la ejecución comienza en el punto de entrada _start, que llama a __libc_start_main
para configurar las cosas, y este último termina llamando a main
. Pero antes de llamar al main
, llama a otras funciones, lo que hace que se escriban varios datos en la pila. El contenido de la pila no se borra entre las llamadas a funciones, por lo que cuando ingresa a main
, su pila contiene restos de las llamadas a funciones anteriores.
Esto solo explica los resultados que obtiene de la pila, vea las otras respuestas con respecto a su enfoque general y suposiciones.
En ambos casos, se obtiene sin inicializar memoria, y no puede hacer suposiciones sobre su contenido.
Cuando el sistema operativo tiene que asignar una nueva página a su proceso (ya sea para su pila o para la arena utilizada por malloc()
), garantiza que no expondrá datos de otros procesos; la forma habitual de asegurarse de eso es llenarlo con ceros (pero es igualmente válido sobrescribir con cualquier otra cosa, incluida incluso una página con un valor de /dev/urandom
- de hecho, algo de depuración malloc()
implementaciones escriben patrones distintos de cero, para detectar suposiciones erróneas como la suya).
Si malloc()
puede satisfacer la solicitud de la memoria ya utilizada y liberada por este proceso, su contenido no se borrará (de hecho, la limpieza no tiene nada que ver con malloc()
y no puede ser, tiene que suceder antes de que la memoria se asigne a su espacio de direcciones). Puede obtener memoria que haya sido escrita previamente por su proceso/programa (por ejemplo, antes de main()
).
En su programa de ejemplo, está viendo un malloc()
región que aún no ha sido escrita por este proceso (es decir, es directamente desde una nueva página) y una pila en la que ha sido escrita (por pre-main()
código en su programa). Si examina más de la pila, encontrará que está lleno de ceros más abajo (en su dirección de crecimiento).
Si realmente desea comprender lo que sucede a nivel del sistema operativo, le recomiendo que omita la capa de la Biblioteca C e interactúe mediante llamadas al sistema como brk()
y mmap()
en su lugar.