Когда C ++ POD-типы инициализируются нулями? - PullRequest
15 голосов
/ 23 июня 2010

Исходя из фона C, я всегда предполагал, что типы POD (например, int) никогда не были автоматически инициализированы нулями в C ++, но кажется, что это было совершенно неправильно!

Насколько я понимаю, только «голые» нестатические значения POD не заполняются нулями, как показано во фрагменте кода. Правильно ли я понял, и есть ли другие важные случаи, которые я пропустил?

static int a;

struct Foo { int a;};

void test()
{
  int b;     
  Foo f;
  int *c = new(int); 
  std::vector<int> d(1);

  // At this point...
  // a is zero
  // f.a is zero
  // *c is zero
  // d[0] is zero
  // ... BUT ... b is undefined     
}  

Ответы [ 5 ]

17 голосов
/ 23 июня 2010

Если вы не изменили a перед вызовом test(), a имеет нулевое значение, поскольку объекты со статической продолжительностью хранения инициализируются нулями при запуске программы.* имеет значение ноль, потому что конструктор, вызванный std::vector<int> d(1), имеет второй параметр, который принимает аргумент по умолчанию;этот второй аргумент копируется во все элементы конструируемого вектора.Аргумент по умолчанию - T(), поэтому ваш код эквивалентен:

std::vector<int> d(1, int());

Вы правы, что b имеет неопределенное значение.также имеют неопределенные значения.Чтобы значения инициализировать их (что для POD-типов совпадает с нулевой инициализацией), вы можете использовать:

Foo f = Foo();      // You could also use Foo f((Foo()))
int* c = new int(); // Note the parentheses
1 голос
/ 23 июня 2010

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

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

Память в сегменте стека постоянно используется повторно. Локальные переменные, фреймы стека функций и т. Д. - все они постоянно используются и используются повторно и не инициализируются каждый раз - только при первой загрузке приложения.

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

Так, например, если вы выделяете Foo в куче, присваиваете его полю значение, удаляете экземпляр Foo, а затем создаете новый Foo в куче, есть вероятность, что новый Foo будет размещен в та же самая точная ячейка памяти, что и у старого Foo, и поэтому его поле будет изначально иметь то же значение, что и поле старого Foo.

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

Иногда, когда вы запускаете приложение в режиме отладки, некоторые среды выполнения в режиме отладки инициализируют данные стека и кучи при каждом выделении (так что ваше поле Foo всегда будет инициализироваться). Однако разные среды выполнения отладки инициализируют данные разными значениями. Некоторые нули инициализируются, а некоторые инициализируются значением «маркер».

Суть в том, что никогда не используйте неинициализированные значения где-либо в вашем коде. Нет абсолютно никакой гарантии, что они будут инициализированы нулями. Кроме того, не забудьте прочитать ранее связанную статью, касающуюся паренов и инициализации по умолчанию против значения, так как это влияет на определение «неинициализированного» значения.

1 голос
/ 23 июня 2010

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

Если я не ошибаюсь, в вашем коде:

  • a должен быть неинициализирован.
  • b должен быть неинициализирован
  • c должен указывать на новый (неинициализированный) int
  • d должен быть инициализирован[0] (как вы правильно догадались)
0 голосов
/ 23 июня 2010

Они не.Версии отладочных битов могут делать это, но обычно они просто помещаются в память и инициализируются тем, что оказалось значением в памяти.

0 голосов
/ 23 июня 2010

Для меня типы POD инициализируются в зависимости от части памяти, которую они размещают. Ваш static int a размещен в сегменте данных, поэтому он имеет значение по умолчанию при запуске. Тем не менее, я думаю, что f не инициализирован в вашем примере ...

...