Подтвердить что ты не робот

Когда типы С++ POD получают ноль-инициализацию?

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

Я понимаю, что только "голые" нестатические значения 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     
}  
4b9b3361

Ответ 1

Предполагая, что вы не модифицировали a перед вызовом test(), a имеет значение 0, потому что объекты со статической продолжительностью хранения инициализируются нулем при запуске программы.

d[0] имеет значение 0, потому что конструктор, вызываемый std::vector<int> d(1), имеет второй параметр, который принимает аргумент по умолчанию; второй аргумент копируется во все элементы построенного вектора. Аргументом по умолчанию является T(), поэтому ваш код эквивалентен:

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

Вы правы, что b имеет неопределенное значение.

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

Foo f = Foo();      // You could also use Foo f((Foo()))
int* c = new int(); // Note the parentheses

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

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

Иногда, когда вы запускаете приложение в режиме отладки, некоторые временные режимы отладки инициализируют данные стека и кучи при каждом размещении (soo ваше поле Foo всегда будет инициализировано). Тем не менее, различные отладочные среды выполнения инициализируют данные для разных значений. Некоторая нуль инициализируется, а некоторые инициализируются значением "маркер".

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

Ответ 4

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

Ответ 5

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