Pregunta ¿Por qué malloc inicializa los valores a 0 en gcc?


Tal vez es diferente de una plataforma a otra, pero

cuando compilo usando gcc y ejecuto el código a continuación, obtengo 0 cada vez en mi ubuntu 11.10.

#include <stdio.h>
#include <stdlib.h>

int main()
{
    double *a = (double*) malloc(sizeof(double)*100)
    printf("%f", *a);
}

¿Por qué malloc se comporta así aunque hay calloc?

¿No significa que hay una sobrecarga de rendimiento no deseada solo para inicializar los valores en 0 incluso si no desea que sea a veces?


EDITAR: Oh, mi ejemplo anterior no iniciaba, pero pasó a usar bloque "nuevo".

Lo que precisamente estaba buscando era por qué lo inicializa cuando asigna un bloque grande:

int main()
{
    int *a = (int*) malloc(sizeof(int)*200000);
    a[10] = 3;
    printf("%d", *(a+10));

    free(a);

    a = (double*) malloc(sizeof(double)*200000);
    printf("%d", *(a+10));
}

OUTPUT: 3
        0 (initialized)

¡Pero gracias por señalar que hay una razón de SEGURIDAD cuando mallocing! (Nunca pensé en eso). Claro que tiene que inicializarse a cero cuando se asigna un bloque nuevo o un bloque grande.


73
2017-11-06 19:05


origen


Respuestas:


Respuesta corta:

No, simplemente resulta ser cero en tu caso.
(También su caso de prueba no muestra que los datos son cero. Solo muestra si un elemento es cero).


Respuesta larga:

Cuando usted llama malloc(), una de dos cosas sucederá:

  1. Se recicla la memoria que se asignó previamente y se liberó del mismo proceso.
  2. Solicita una nueva página (s) del sistema operativo.

En el primer caso, la memoria contendrá los datos sobrantes de las asignaciones anteriores. Entonces no será cero. Este es el caso habitual cuando se realizan pequeñas asignaciones.

En el segundo caso, la memoria será del SO. Esto ocurre cuando el programa se queda sin memoria, o cuando solicita una asignación muy grande. (como es el caso en tu ejemplo)

Aquí está el truco: La memoria proveniente del SO se pondrá a cero para seguridad razones.*

Cuando el SO le da memoria, podría haber sido liberado de un proceso diferente. Para que la memoria pueda contener información confidencial, como una contraseña. Por lo tanto, para evitar que lea esos datos, el sistema operativo lo pondrá a cero antes de que se lo proporcione.

* Noto que el estándar C no dice nada sobre esto. Esto es estrictamente un comportamiento de sistema operativo. Por lo tanto, esta reducción a cero puede o no estar presente en los sistemas donde la seguridad no es una preocupación.


Para dar más de un rendimiento de fondo a esto: 

Como @R. menciona en los comentarios, esta puesta a cero es la razón por la que siempre debes utilizar calloc() en lugar de malloc() + memset(). calloc() puede aprovechar este hecho para evitar una separación memset().


Por otro lado, esta reducción a cero es a veces un cuello de botella de rendimiento. En algunas aplicaciones numéricas (como el FFT fuera de lugar), necesita asignar una gran cantidad de memoria de memoria virtual. Úselo para realizar cualquier algoritmo, luego libérelo.

En estos casos, la puesta a cero es innecesaria y equivale a una sobrecarga pura. 

El ejemplo más extremo que he visto es una sobrecarga de cero de 20 segundos para una operación de 70 segundos con un buffer de 48 GB. (Aproximadamente 30% de sobrecarga) (De acuerdo: la máquina tenía una falta de ancho de banda de memoria).

La solución obvia es simplemente reutilizar la memoria manualmente. Pero eso a menudo requiere romper con las interfaces establecidas. (especialmente si es parte de una rutina de biblioteca)


162
2017-11-06 19:09



El sistema operativo normalmente borrará las páginas de memoria nuevas que envía a su proceso para que no pueda ver los datos de un proceso anterior. Esto significa que la primera vez que inicializa una variable (o malloc algo) a menudo será cero, pero si alguna vez reutiliza esa memoria (liberándola y mallocándola de nuevo, por ejemplo), entonces todas las apuestas estarán desactivadas.

Esta inconsistencia es precisamente por qué las variables no inicializadas son un error difícil de encontrar.


En cuanto a los gastos generales de rendimiento no deseados, evitar comportamientos no especificados es probablemente más importante. Cualquier pequeña mejora de rendimiento que pueda obtener en este caso no compensará los errores difíciles de encontrar con los que tendrá que lidiar si alguien modifica levemente los códigos (rompiendo suposiciones previas) o los puertos a otro sistema (donde las suposiciones podrían haber sido inválidas) en primer lugar).


20
2017-11-06 19:10



Por qué asumes eso malloc() se inicializa a cero? Resulta que la primera llamada a malloc() da como resultado una llamada a sbrk o mmap llamadas al sistema, que asignan una página de memoria del sistema operativo. El sistema operativo está obligado a proporcionar memoria inicializada cero por razones de seguridad (de lo contrario, los datos de otros procesos se vuelven visibles). Entonces, puede que piense allí: el sistema operativo pierde tiempo para poner a cero la página. ¡Pero no! En Linux, hay una página singleton especial en todo el sistema llamada 'página cero' y esa página se mapeará como Copy-On-Write, lo que significa que solo cuando escriba en esa página, el sistema operativo asignará otra página y Inicialízalo. Así que espero que esto responda a su pregunta con respecto al rendimiento. El modelo de paginación de memoria permite que el uso de la memoria sea una especie de vago al admitir la capacidad de mapeo múltiple de la misma página más la capacidad de manejar el caso cuando se produce la primera escritura.

Si llamas free(), el glibc asignador devolverá la región a sus listas libres, y cuando malloc() se llama de nuevo, puede obtener esa misma región, pero sucia con los datos anteriores. Finalmente, free() puede devolver la memoria al sistema operativo al volver a llamar al sistema.

Tenga en cuenta que glibc  página man en malloc() dice estrictamente que la memoria no se borra, por lo tanto, por el "contrato" en la API, no se puede asumir que se borre. Aquí está el extracto original:

malloc () asigna bytes de tamaño y devuelve un puntero a la memoria asignada.
  La memoria no está borrada. Si el tamaño es 0, entonces malloc () devuelve NULL,   o un valor de puntero único que luego puede pasar con éxito a free ().

Si lo desea, puede leer más sobre esa documentación si le preocupa el rendimiento u otros efectos secundarios.


15
2017-11-06 19:11



Modifiqué tu ejemplo para que contenga 2 asignaciones idénticas. Ahora es fácil ver malloc no inicializa la memoria a cero.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    {
      double *a = malloc(sizeof(double)*100);
      *a = 100;
      printf("%f\n", *a);
      free(a);
    }
    {
      double *a = malloc(sizeof(double)*100);
      printf("%f\n", *a);
      free(a);
    }

    return 0;
}

Salida con gcc 4.3.4

100.000000
100.000000

13
2017-11-06 19:19



El estándar no dicta que malloc() debe inicializar los valores a cero. Simplemente ocurre en su plataforma que podría establecerse en cero, o podría haber sido cero en el momento específico en que lee ese valor.


2
2017-11-06 19:09



Tu código no demuestra eso malloc inicializa su memoria en 0. Eso podría ser hecho por el sistema operativo, antes de que comience el programa. Para ver cuál es el caso, escriba un valor diferente en la memoria, libérelo y vuelva a llamar a malloc. Probablemente obtendrá la misma dirección, pero tendrá que verificar esto. Si es así, puede ver qué contiene. ¡Haznos saber!


2
2017-11-06 19:09



De gnu.org:

Los bloques muy grandes (mucho más grandes que una página) se asignan con mmap (anónimo o vía / dev / zero) por esta implementación.


2
2017-11-06 19:15



¿Sabes que definitivamente se está inicializando? ¿Es posible que el área devuelta por malloc () solo tenga 0 al principio?


0
2017-11-06 19:09