Почему malloc инициализирует значения в 0 в gcc? - PullRequest
74 голосов
/ 06 ноября 2011

Возможно, это отличается от платформы к платформе, но

, когда я компилирую с использованием gcc и запускаю приведенный ниже код, я получаю 0 каждый раз в своей Ubuntu 11.10.

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

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

Почемуведет себя так, как malloc ведет себя так, даже если есть calloc?

Не значит ли это, что из-за нежелательных издержек производительности просто инициализируются значения 0, даже если вы не хотите, чтобы это было иногда?


РЕДАКТИРОВАТЬ: О, мой предыдущий пример не был инициализировать, но случайно использовал «свежий» блок.

Я точно искал, почему он инициализировал его, когда выделяет большой блок:

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)

Но спасибо за то, что указали, что есть причина БЕЗОПАСНОСТИ при неправильном обращении!(Никогда не думал об этом).Конечно, он должен инициализироваться в ноль при выделении нового блока или большого блока.

Ответы [ 9 ]

168 голосов
/ 06 ноября 2011

Краткий ответ:

Это не так, просто в вашем случае он равен нулю.
(Также ваш тестовый пример не показывает, что данные равны нулю. Он показывает только, если один элемент равен нулю.)


Длинный ответ:

Когда вы звоните malloc(), произойдет одно из двух:

  1. Перерабатывает память, которая была ранее выделена и освобождена из того же процесса.
  2. Запрашивает новые страницы из операционной системы.

В первом случае память будет содержать данные, оставшиеся от предыдущих выделений. Так что это не будет ноль. Это обычный случай при выполнении небольших выделений.

Во втором случае память будет от ОС. Это происходит, когда программе не хватает памяти - или когда вы запрашиваете очень большое выделение. (как в вашем примере)

Вот подвох: Память, поступающая из ОС, обнуляется по соображениям безопасности . *

Когда ОС предоставляет вам память, она могла быть освобождена из другого процесса. Так что память может содержать конфиденциальную информацию, такую ​​как пароль. Поэтому, чтобы предотвратить чтение таких данных, ОС обнулит их, прежде чем они предоставят вам.

* Замечу, что в стандарте C об этом ничего не говорится. Это строго поведение ОС. Так что это обнуление может присутствовать или не присутствовать в системах, где безопасность не имеет значения.


Чтобы дать больше фона производительности для этого:

Как @R. упоминает в комментариях, это обнуление, поэтому вы всегда должны использовать calloc() вместо malloc() + memset(). calloc() может воспользоваться этим фактом, чтобы избежать отдельного memset().


С другой стороны, это обнуление иногда является узким местом производительности. В некоторых числовых приложениях (например, FFT вне места) вам нужно выделить огромный кусок памяти. Используйте его для выполнения любого алгоритма, затем освободите его.

В этих случаях обнуление не требуется и составляет чистые накладные расходы.

Самый крайний пример, который я видел, это 20-секундные накладные расходы на обнуление для 70-секундной операции с чистым буфером 48 ГБ. (Примерно 30% накладных расходов.) (Конечно: у машины не хватило пропускной способности памяти.)

Очевидное решение - просто повторно использовать память вручную. Но это часто требует прорыва через установленные интерфейсы. (особенно если это часть библиотечной процедуры)

21 голосов
/ 06 ноября 2011

ОС обычно очищает свежие страницы памяти, которые она отправляет вашему процессу, поэтому она не может просматривать данные более старого процесса.Это означает, что в первый раз, когда вы инициализируете переменную (или что-то malloc), она часто будет равна нулю, но если вы когда-нибудь снова будете использовать эту память (например, путем ее освобождения и повторного размещения), тогда все ставки отключены.

Именно из-за этой несогласованности не так легко найти неинициализированные переменные.


Что касается нежелательных издержек производительности, избегание неопределенного поведения, вероятно, более важно .Какой бы небольшой прирост производительности вы ни получили в этом случае, это не компенсирует трудность поиска ошибок, с которыми вам придется столкнуться, если кто-то слегка модифицирует коды (нарушая предыдущие предположения) или перенесет их в другую систему (где предположения могут быть недействительными).в первую очередь).

17 голосов
/ 06 ноября 2011

Почему вы предполагаете, что malloc() инициализируется нулем?Так уж получилось, что первый вызов malloc() приводит к вызову системных вызовов sbrk или mmap, которые выделяют страницу памяти из ОС.ОС обязана предоставлять инициализированную нулем память по соображениям безопасности (в противном случае данные из других процессов становятся видимыми!).Так что вы можете подумать - ОС тратит время на обнуление страницы.Но нет!В Linux есть специальная общесистемная одноэлементная страница, называемая «нулевой страницей», и эта страница будет отображаться как Copy-On-Write, что означает, что только когда вы действительно будете писать на этой странице, ОС выделит другую страницу иинициализировать это.Поэтому я надеюсь, что это ответит на ваш вопрос относительно производительности.Модель подкачки памяти позволяет использовать ленивую память, поддерживая возможность многократного отображения одной и той же страницы, а также возможность обрабатывать случай, когда происходит первая запись.

Если вы вызываете free(), распределитель glibc вернет регион в его свободные списки, и при повторном вызове malloc() вы можете получить тот же регион, но с предыдущими данными.В конце концов, free() может вернуть память в ОС путем повторного вызова системных вызовов.

Обратите внимание, что glibc * man-страница на malloc() строго говорит, что память не очищенапоэтому, согласно «контракту» на API, вы не можете предполагать, что он действительно очищен.Вот оригинальная выдержка:

malloc () распределяет байты размера и возвращает указатель на выделенную память.
Память не очищена.Если size равен 0, то malloc () возвращает либо NULL, либо уникальное значение указателя, которое впоследствии может быть успешно передано free ().

Если вы хотите, вы можете прочитать об этом подробнеедокументация, если вы беспокоитесь о производительности или других побочных эффектах.

14 голосов
/ 06 ноября 2011

Я изменил ваш пример, чтобы он содержал 2 одинаковых распределения. Теперь легко видеть, что malloc не обнуляет инициализацию памяти.

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

Вывод с gcc 4.3.4

100.000000
100.000000
3 голосов
/ 06 ноября 2011

С gnu.org :

Очень большие блоки (намного больше, чем страница) выделяются с помощью mmap (анонимно или через / dev / zero ) этой реализацией.

2 голосов
/ 06 ноября 2011

Ваш код не демонстрирует, что malloc инициализирует свою память на 0. Это может быть сделано операционной системой до запуска программы. Чтобы понять, в чем дело, запишите в память другое значение, освободите его и снова вызовите malloc. Вы, вероятно, получите тот же адрес, но вам придется это проверить. Если это так, вы можете посмотреть, что в нем содержится. Дайте нам знать!

2 голосов
/ 06 ноября 2011

Стандарт не предписывает, что malloc() должен инициализировать значения в ноль.На вашей платформе просто случается, что он может быть установлен на ноль, или он мог быть нулем в конкретный момент, когда вы читаете это значение.

0 голосов
/ 06 ноября 2011

Никогда никогда рассчитывать на любой компилятор для генерации кода, который инициализирует память для чего угодно.malloc просто возвращает указатель на n байтов памяти куда-то черт возьми, он даже может быть в разделе подкачки.

Если содержимое памяти критично, инициализируйте его самостоятельно.

0 голосов
/ 06 ноября 2011

Знаете ли вы, что он определенно инициализируется? Возможно ли, что область, возвращаемая функцией malloc (), часто имеет 0 в начале?

...