Инициализация нуля в C ++. Почему `b` в этой программе не инициализирован, а` a` инициализирован? - PullRequest
0 голосов
/ 24 января 2019

Согласно принятому (и единственному) ответу на этот вопрос переполнения стека ,

Определение конструктора с помощью

MyTest() = default;

вместо этого инициализирует ноль объекта.

Тогда почему следующее,

#include <iostream>

struct foo {
    foo() = default;
    int a;
};

struct bar {
    bar();
    int b;
};

bar::bar() = default;

int main() {
    foo a{};
    bar b{};
    std::cout << a.a << ' ' << b.b;
}

выдаст этот вывод:

0 32766

Оба конструктора определены по умолчанию? Правильно? И для типов POD, инициализация по умолчанию - инициализация нуля.

А согласно принятому ответу на этот вопрос ,

  1. Если элемент POD не инициализирован ни в конструкторе, ни через C ++ 11 инициализация в классе, инициализируется по умолчанию.

  2. Ответ один и тот же, независимо от стека или кучи.

  3. В C ++ 98 (а не после) новый int () был указан как выполняющий нулевая инициализация.

Несмотря на попытку обернуть мою (хотя и крошечную ) голову конструкторы по умолчанию и инициализация по умолчанию , я не смог придумать объяснение.

Ответы [ 4 ]

0 голосов
/ 27 января 2019

Мех, я попытался запустить предоставленный вами фрагмент как test.cpp, используя gcc & clang и несколько уровней оптимизации:

steve@steve-pc /tmp> g++ -o test.gcc.O0 test.cpp
                                                                              [ 0s828 | Jan 27 01:16PM ]
steve@steve-pc /tmp> g++ -o test.gcc.O2 -O2 test.cpp
                                                                              [ 0s901 | Jan 27 01:16PM ]
steve@steve-pc /tmp> g++ -o test.gcc.Os -Os test.cpp
                                                                              [ 0s875 | Jan 27 01:16PM ]
steve@steve-pc /tmp> ./test.gcc.O0
0 32764                                                                       [ 0s004 | Jan 27 01:16PM ]
steve@steve-pc /tmp> ./test.gcc.O2
0 0                                                                           [ 0s004 | Jan 27 01:16PM ]
steve@steve-pc /tmp> ./test.gcc.Os
0 0                                                                           [ 0s003 | Jan 27 01:16PM ]
steve@steve-pc /tmp> clang++ -o test.clang.O0 test.cpp
                                                                              [ 1s089 | Jan 27 01:17PM ]
steve@steve-pc /tmp> clang++ -o test.clang.Os -Os test.cpp
                                                                              [ 1s058 | Jan 27 01:17PM ]
steve@steve-pc /tmp> clang++ -o test.clang.O2 -O2 test.cpp
                                                                              [ 1s109 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 274247888                                                                   [ 0s004 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.Os
0 0                                                                           [ 0s004 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.O2
0 0                                                                           [ 0s004 | Jan 27 01:17PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 2127532240                                                                  [ 0s002 | Jan 27 01:18PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 344211664                                                                   [ 0s004 | Jan 27 01:18PM ]
steve@steve-pc /tmp> ./test.clang.O0
0 1694408912                                                                  [ 0s004 | Jan 27 01:18PM ]

Так что здесь интересно, он ясно показывает, что сборка clang O0чтение случайных чисел, предположительно стекового пространства.

Я быстро включил свою IDA, чтобы посмотреть, что происходит:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rax
  int result; // eax
  unsigned int v6; // [rsp+8h] [rbp-18h]
  unsigned int v7; // [rsp+10h] [rbp-10h]
  unsigned __int64 v8; // [rsp+18h] [rbp-8h]

  v8 = __readfsqword(0x28u); // alloca of 0x28
  v7 = 0; // this is foo a{}
  bar::bar((bar *)&v6); // this is bar b{}
  v3 = std::ostream::operator<<(&std::cout, v7); // this is clearly 0
  v4 = std::operator<<<std::char_traits<char>>(v3, 32LL); // 32 = 0x20 = ' '
  result = std::ostream::operator<<(v4, v6); // joined as cout << a.a << ' ' << b.b, so this is reading random values!!
  if ( __readfsqword(0x28u) == v8 ) // stack align check
    result = 0;
  return result;
}

Теперь, что делает bar::bar(bar *this)?

void __fastcall bar::bar(bar *this)
{
  ;
}

Хм, ничего.Нам пришлось прибегнуть к использованию ассемблера:

.text:00000000000011D0                               ; __int64 __fastcall bar::bar(bar *__hidden this)
.text:00000000000011D0                                               public _ZN3barC2Ev
.text:00000000000011D0                               _ZN3barC2Ev     proc near               ; CODE XREF: main+20↓p
.text:00000000000011D0
.text:00000000000011D0                               var_8           = qword ptr -8
.text:00000000000011D0
.text:00000000000011D0                               ; __unwind {
.text:00000000000011D0 55                                            push    rbp
.text:00000000000011D1 48 89 E5                                      mov     rbp, rsp
.text:00000000000011D4 48 89 7D F8                                   mov     [rbp+var_8], rdi
.text:00000000000011D8 5D                                            pop     rbp
.text:00000000000011D9 C3                                            retn
.text:00000000000011D9                               ; } // starts at 11D0
.text:00000000000011D9                               _ZN3barC2Ev     endp

Так что да, просто, ничего, что в основном делает конструктор, это this = this.Но мы знаем, что он на самом деле загружает случайные неинициализированные адреса стека и печатает его.

Что если мы явно предоставим значения для двух структур?

#include <iostream>

struct foo {
    foo() = default;
    int a;
};

struct bar {
    bar();
    int b;
};

bar::bar() = default;

int main() {
    foo a{0};
    bar b{0};
    std::cout << a.a << ' ' << b.b;
}

Ударьте по ляпу, упс:

steve@steve-pc /tmp> clang++ -o test.clang.O0 test.cpp
test.cpp:17:9: error: no matching constructor for initialization of 'bar'
    bar b{0};
        ^~~~
test.cpp:8:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion
      from 'int' to 'const bar' for 1st argument
struct bar {
       ^
test.cpp:8:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion
      from 'int' to 'bar' for 1st argument
struct bar {
       ^
test.cpp:13:6: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
bar::bar() = default;
     ^
1 error generated.
                                                                              [ 0s930 | Jan 27 01:35PM ]

Аналогичная судьба и с g ++:

steve@steve-pc /tmp> g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:17:12: error: no matching function for call to ‘bar::bar(<brace-enclosed initializer list>)’
     bar b{0};
            ^
test.cpp:8:8: note: candidate: ‘bar::bar()’
 struct bar {
        ^~~
test.cpp:8:8: note:   candidate expects 0 arguments, 1 provided
test.cpp:8:8: note: candidate: ‘constexpr bar::bar(const bar&)’
test.cpp:8:8: note:   no known conversion for argument 1 from ‘int’ to ‘const bar&’
test.cpp:8:8: note: candidate: ‘constexpr bar::bar(bar&&)’
test.cpp:8:8: note:   no known conversion for argument 1 from ‘int’ to ‘bar&&’
                                                                              [ 0s718 | Jan 27 01:35PM ]

Таким образом, это означает, что это фактически прямая инициализация bar b(0), не агрегатная инициализация.

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

bar::bar() {
  this.b = 1337; // whoa
}

Компилятор не достаточно умен, чтобы выводить это какбезоперационный / встроенный вызов в неоптимизированной стадии.

0 голосов
/ 24 января 2019

Проблема здесь довольно тонкая.Можно подумать, что

bar::bar() = default;

даст вам конструктор по умолчанию, сгенерированный компилятором, и это так, но теперь он считается предоставленным пользователем. [dcl.fct.def.default] / 5 состояния:

Явно-дефолтные функции и неявно объявленные функции вместе называются дефолтными функциями, и реализация должна предоставлять неявные определениядля них ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign]), что может означать определение их как удаленных. Функция предоставляется пользователем, если она объявлена ​​пользователем и не была явно задана по умолчанию или удалена при первом объявлении. Предоставленная пользователем функция явно по умолчанию (т. Е. Явно задана по умолчанию после первого объявления) определена вточка, где это явно по умолчанию;если такая функция неявно определена как удаленная, программа является некорректной.[Примечание: Объявление функции по умолчанию после ее первого объявления может обеспечить эффективное выполнение и краткое определение при одновременном включении стабильного двоичного интерфейса в развивающуюся базу кода.- конец примечания]

выделение минное

Итак, мы можем видеть, что, поскольку вы не указали bar() по умолчанию, когда впервые его объявили, он теперь считаетсяпользователь предоставил.Из-за этого [dcl.init] /8.2

, если T - это (возможно, cv-квалифицированный) тип класса без предоставленного пользователем или удаленного конструктора по умолчанию, тогда объектинициализируется нулями, и проверяются семантические ограничения для инициализации по умолчанию, и если T имеет нетривиальный конструктор по умолчанию, объект инициализируется по умолчанию;

больше не применяется, и мы не являемся значениеминициализирует b, но вместо этого по умолчанию инициализирует его для [dcl.init] /8.1

, если T является (возможно, cv-квалифицированным) типом класса ([class]) слибо без конструктора по умолчанию ([class.default.ctor]), либо конструктор по умолчанию, предоставленный или удаленный пользователем, тогда объект инициализируется по умолчанию;

0 голосов
/ 24 января 2019

С cppreference :

Инициализация агрегата инициализирует агрегаты.Это форма инициализации списка.

Агрегат является одним из следующих типов:

[snip]

  • тип класса [snip], который имеет

    • [snip] (есть варианты для разных стандартных версий)

    • нет пользовательских, унаследованных или явных конструкторов (допускаются явно заданные по умолчанию или удаленные конструкторы)

    • [snip] (есть еще правила, которые применяются к обоим классам)

С учетом этого определения foo является агрегатом, а bar - нет (он имеет предоставленный пользователем конструктор, не являющийся дефолтом).

Поэтому для foo, T object {arg1, arg2, ...}; - это синтаксис для инициализации агрегата.

Эффекты инициализации агрегата:

  • [snip] (некоторые детали не имеют отношения к этому случаю)

  • Если количество предложений инициализатора меньше, чем количество членов, или список инициализаторов полностью пуст, оставшиесяning члены инициализируются значением .

Следовательно, a.a является значением инициализированным, что для int означает нулевую инициализацию.

Для bar, T object {};, с другой стороны, это инициализация значения (экземпляра класса, а не инициализация значения членов!).Поскольку это тип класса с конструктором по умолчанию, вызывается конструктор по умолчанию.Заданный по умолчанию конструктор по умолчанию инициализирует элементы (из-за отсутствия инициализаторов элементов), который в случае int (с нестатическим хранилищем) оставляет b.b с неопределенным значением.

И для типов pod, инициализация по умолчанию - инициализация нуля.

Нет.Это неправильно.


PS Пару слов о вашем эксперименте и вашем заключении: если вы видите, что результат равен нулю, не обязательно означает, что переменная была инициализирована нулем.Ноль - вполне возможное число для значения мусора.

для этого я запустил программу, может быть, 5 ~ 6 раз перед публикацией и около 10 раз сейчас, а всегда равно нулю.b немного меняется.

Тот факт, что значение было одно и то же несколько раз, не обязательно означает, что оно также было инициализировано.

Я также пытался с set (CMAKE_CXX_STANDARD14).Результат был одинаковым.

Тот факт, что результат одинаков для нескольких опций компилятора, не означает, что переменная инициализирована.(Хотя в некоторых случаях изменение стандартной версии может изменить ее инициализацию).

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

В C ++ не существует гарантированного способа заставить значение неинициализированного значения выглядеть ненулевым.

Единственный способ узнать, что переменная инициализирована, - это сравнить программу с правилами языка иубедитесь, что правила говорят, что это инициализировано.В этом случае a.a действительно инициализируется.

0 голосов
/ 24 января 2019

Различие в поведении происходит от того факта, что, согласно [dcl.fct.def.default]/5, bar::bar является предоставленным пользователем , где foo::foo не является 1 .Как следствие, foo::foo будет инициализировать значение своих членов (что означает: инициализация нуля foo::a), но bar::bar останется неинициализированным 2 .


1) [dcl.fct.def.default]/5

Функция предоставляется пользователем, если она объявлена ​​пользователем , а не явнопо умолчанию или удален в своем первом объявлении.

2)

From [dcl.init # 6] :

Инициализация значения объекта типа T означает:

  • , если T является (возможно, cv-квалифицированным) типом класса без конструктора по умолчанию ([class.ctor]) или конструктор по умолчанию, предоставленный или удаленный пользователем, тогда объект инициализируется по умолчанию;

  • , если T - (возможно, cv тип класса без предоставленного пользователем или удаленного конструктора по умолчанию, тогда объект инициализируется нулями , и проверяются семантические ограничения для инициализации по умолчанию, и если T имеет не-tпростой конструктор по умолчанию, объект инициализируется по умолчанию;

  • ...

с [dcl.init.list] :

Инициализация списка объекта или ссылки типа T определяется следующим образом:

  • ...

  • В противном случае, если список инициализаторов не имеет элементов и T является типом класса с конструктором по умолчанию, объект инициализируется значением.

От Ответ Витторио Ромео

...