Почему POD в структуре инициализируется нулем неявным конструктором при создании объекта в куче или временного объекта в стеке?

Стандарт и книга C ++ говорят, что конструктор по умолчанию для членов типа класса вызывается неявно сгенерированным конструктором по умолчанию, но встроенные типы не инициализируются. Однако в этой тестовой программе я получаю неожиданные результаты при размещении объекта в куче или при использовании временного объекта:

#include<iostream>


struct Container
{
    int n;
};

int main()
{
    Container c;
    std::cout << "[STACK] Num: " << c.n << std::endl;

    Container *pc = new Container();
    std::cout << "[HEAP]  Num: " << pc->n << std::endl;
    delete pc;

    Container tc = Container();
    std::cout << "[TEMP]  Num: " << tc.n << std::endl;

}

Я получаю такой вывод:

[STACK] Num: -1079504552
[HEAP]  Num: 0
[TEMP]  Num: 0

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


person Jacobo de Vera    schedule 08.02.2011    source источник
comment
возможный дубликат инициализации структуры по умолчанию в C ++   -  person sharptooth    schedule 08.02.2011
comment
я предполагаю, что сама куча инициализируется нулями   -  person davka    schedule 08.02.2011
comment
Я думал то же самое о случае с кучей, возможно, это были просто нули случайно, но я нахожу случай временного объекта удивительным.   -  person Jacobo de Vera    schedule 08.02.2011
comment
см. ссылку, опубликованную @sharptooth, в ней есть объяснение временного случая   -  person davka    schedule 08.02.2011
comment
Большинство операционных систем в качестве меры безопасности обнуляют страницы памяти перед их передачей процессу (чтобы процесс не мог проверять память других процессов). Результатом этого является то, что чаще всего простой тестовый пример, в котором только маллоки получают инициализированную память (не совсем, но выглядит так), в то время как у стека больше шансов быть измененным с помощью предыдущие вызовы функций.   -  person David Rodríguez - dribeas    schedule 09.02.2011


Ответы (2)


Это ожидаемое поведение. Есть два понятия: «инициализация по умолчанию» и «инициализация значения». Если вы не упоминаете какой-либо инициализатор, объект является «инициализированным по умолчанию», в то время как если вы его упоминаете, даже как () для конструктора по умолчанию, объект является «инициализированным значением». Когда конструктор определен, оба случая вызывают конструктор по умолчанию. Но для встроенных типов «инициализация значения» обнуляет память, а «инициализация по умолчанию» - нет.

Итак, когда вы инициализируете:

Type x;

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

Type x = {}; // only works for struct/class without constructor
Type x = Type();
Type x{}; // C++11 only

примитивный тип (или примитивные члены структуры) будет инициализирован VALUE.

Аналогично для:

struct X { int x; X(); };

если вы определяете конструктор

X::X() {}

член x будет неинициализирован, но если вы определите конструктор

X::X() : x() {}

он будет инициализирован VALUE. Это относится и к new, поэтому

new int;

должен дать вам неинициализированную память, но

new int();

должен дать вам память, инициализированную до нуля. К сожалению синтаксис:

Type x();

не допускается из-за двусмысленности грамматики и

Type x = Type();

обязан вызвать конструктор по умолчанию с последующим копированием-конструктором, если они оба указаны и не являются встроенными.

C ++ 11 вводит новый синтаксис,

Type x{};

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

Более подробное обсуждение можно найти, например, в документации Boost.ValueInitialized.

person Jan Hudec    schedule 08.02.2011
comment
отличный ответ, но как насчет std :: make_unique ‹T› (), будет ли оно инициализировать значение переменной? - person tommyk; 09.11.2016

Короткий ответ: пустая скобка выполняет инициализацию значения.

Когда вы вместо этого скажете Container *pc = new Container;, вы увидите другое поведение.

person fredoverflow    schedule 08.02.2011
comment
@FredOverflow, верно, но мой gcc инициализирует типы POD значением 0 с круглыми скобками или без них. - person UmmaGumma; 08.02.2011
comment
@Ashot: Это совпадение, тоже случается на моей машине. Просто создайте еще парочку в куче без скобок, и вы получите другие значения. - person fredoverflow; 08.02.2011
comment
@FredOverflow Сразу после выделения большого количества памяти я наконец получаю ненулевые данные :) Спасибо. - person UmmaGumma; 08.02.2011
comment
@ Ашот Мартиросян: Вы можете выполнить этот тест, чтобы увидеть, действительно ли он инициализируется или нет: struct test { int x; }; void foo() { test t; std::cout << t.x << std::endl; ++t.x; } int main() { foo(); foo(); foo(); } Общий шаблон (если компилятор не встроен) состоит в том, что t будет создан в одной и той же позиции памяти в последовательных вызовах, в первый раз скорее всего это будет 0, но потом это изменится. Если код инициализируется, вы всегда будете получать 0 во всех вызовах. - person David Rodríguez - dribeas; 08.02.2011
comment
@David Rodríguez - dribeas: Этот код всегда (во всех моих тестах) выводит 0 :) Не забывайте, что вызов foo () также использует память, поэтому на самом деле ваши объекты t не помещаются в одно и то же место в стеке :) . - person UmmaGumma; 08.02.2011
comment
@ Ашот Мартиросян: Если каждый вызов метода требует дополнительной памяти, вы слишком легко получите переполнение стека: for (int i =0; i < 1000000000; ++i ) { foo(); }. Каждый вызов foo() будет извлекать из стека свою долю, и он будет освобожден, когда функция вернется (обычно вызывающий, но это деталь реализации). Последовательные вызовы одной и той же функции должны повторно использовать одну и ту же часть стека. Вы добавляли код в тест? Какой компилятор вы используете? Приведенный выше код показывает ожидаемые результаты в g ++. - person David Rodríguez - dribeas; 09.02.2011
comment
@David Rodríguez - dribeas Я думаю, вы не понимаете, что я говорю. Компилятор сгенерирует один код вызова функции (я имею в виду выделенную память) в цикле. При вызове функции 2 раза без цикла (foo();foo()) будет сгенерировано 2 вызова функции, поэтому ваш новый код не равен предыдущему. Этот будет работать так, как вы ожидаете, а предыдущий - нет. Я тоже использую g ++. - person UmmaGumma; 09.02.2011