Zgodnie z zaakceptowaną (i jedyną) odpowiedzią na to pytanie dotyczące przepełnienia stosu ,
Definiowanie konstruktora za pomocą
MyTest() = default;
zamiast tego wyzeruje obiekt.
W takim razie dlaczego następujące
#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;
}
wygeneruj ten wynik:
0 32766
Oba zdefiniowane konstruktory są domyślne? Dobrze? W przypadku typów POD domyślną inicjalizacją jest inicjalizacja zerowa.
I zgodnie z przyjętą odpowiedzi na to pytanie ,
Jeśli element członkowski POD nie zostanie zainicjowany w konstruktorze ani za pośrednictwem inicjalizacji w klasie C ++ 11, jest inicjowany domyślnie.
Odpowiedź jest taka sama, niezależnie od stosu lub stosu.
W C ++ 98 (i nie później), new int () zostało określone jako wykonujące zerową inicjalizację.
Pomimo próby owinięcia mojej (choć malutkiej ) głowy wokół domyślnych konstruktorów i domyślnej inicjalizacji , nie mogłem wymyślić wyjaśnienia.
źródło
bar
Konstruktor jest dostarczany przez użytkownika, podczas gdyfoo
konstruktor jest konstruktorem domyślnym.a
wynosi zero.b
nie jest. Wydaje się, żea
jest zainicjowany.bar::bar()
jest widoczna wmain()
- może być zdefiniowana w oddzielnej jednostce kompilacji i zrobić coś bardzo nietrywialnego, podczas gdymain()
widoczna jest tylko deklaracja. Myślę, że zgodzisz się, że to zachowanie nie powinno się zmieniać w zależności od tego, czybar::bar()
umieścisz definicję w oddzielnej jednostce kompilacji, czy nie (nawet jeśli cała sytuacja jest nieintuicyjna).int a = 0;
może chcesz być naprawdę wyraźny.Odpowiedzi:
Problem jest tutaj dość subtelny. Można by tak pomyśleć
dałoby ci domyślny konstruktor wygenerowany przez kompilator i robi, ale teraz jest uważany za dostarczony przez użytkownika. [dcl.fct.def.default] / 5 stwierdza:
podkreślenie moje
Widzimy więc, że skoro nie ustawiłeś wartości domyślnej,
bar()
gdy po raz pierwszy ją zadeklarowałeś, jest teraz uważany za dostarczony przez użytkownika. Z tego powodu [dcl.init] /8.2już nie ma zastosowania i nie inicjalizujemy wartości,
b
ale zamiast tego domyślnie inicjalizujemy ją zgodnie z [dcl.init] /8.1źródło
(*_*)
... Jeśli nawet użyję podstawowych konstrukcji języka, muszę przeczytać drobnym drukiem szkicu języka, a następnie Alleluja! Ale prawdopodobnie wydaje się, że tak właśnie mówisz.bar::bar() = default
wyjście poza linię działa tak samo, jak wykonaniebar::bar(){}
inline.Różnica w zachowaniu wynika z faktu, że, zgodnie z
[dcl.fct.def.default]/5
,bar::bar
jest dostarczane przez użytkownika, gdziefoo::foo
nie jest 1 . W konsekwencjifoo::foo
będzie cenią zainicjować swoich członków (czyli: zero-zainicjowaćfoo::a
), alebar::bar
pozostanie niezainicjowanymi 2 .1)
[dcl.fct.def.default]/5
2)
Z odpowiedzi Vittorio Romeo
źródło
Od cppreference :
Biorąc pod uwagę tę definicję,
foo
jest agregatem, abar
nie (ma dostarczony przez użytkownika, niedomyślny konstruktor).Dlatego dla
foo
,T object {arg1, arg2, ...};
jest składnia do inicjalizacji agregacji.Dlatego
a.a
wartość jest inicjalizowana, coint
oznacza zerową inicjalizację.Na
bar
,T object {};
z drugiej strony jest inicjalizacji wartość (na przykład klasy, a nie wartość inicjalizacji członków!). Ponieważ jest to typ klasy z domyślnym konstruktorem, wywoływany jest domyślny konstruktor. Domyślny konstruktor, który zdefiniowałeś domyślnie, inicjuje elementy członkowskie (z powodu braku inicjatorówint
składowych) , które w przypadku (z niestatyczną pamięcią) pozostawiająb.b
nieokreśloną wartość.Nie. To jest złe.
PS Kilka słów na temat twojego eksperymentu i twojego wniosku: Widzenie, że wynik ma wartość zero, niekoniecznie oznacza, że zmienna została zainicjalizowana zero. Zero jest jak najbardziej możliwą liczbą dla wartości śmieciowej.
Fakt, że wartość była wielokrotnie taka sama, niekoniecznie oznacza, że została również zainicjowana.
Fakt, że wynik jest taki sam w przypadku wielu opcji kompilatora, nie oznacza, że zmienna została zainicjalizowana. (Chociaż w niektórych przypadkach zmiana wersji standardowej może zmienić, czy jest ona zainicjowana).
W C ++ nie ma żadnego gwarantowanego sposobu, aby niezainicjowana wartość wyglądała na niezerową.
Jedynym sposobem, aby dowiedzieć się, że zmienna jest zainicjalizowana, jest porównanie programu z regułami języka i sprawdzenie, czy reguły mówią, że została zainicjalizowana. W tym przypadku
a.a
jest rzeczywiście inicjalizowany.źródło
a
inicjalizowane jest jak to się stało . Myślałem, żea
jest domyślnie zainicjowany, a domyślna inicjalizacja dla członka POD to inicjalizacja zerowa. Naa
szczęście zawsze zbliża się do zera, bez względu na to, ile razy uruchamiam ten program.Then how come a is initialized.
Ponieważ jest zainicjowana wartość.I was thinking a is default initialized
Nie jest.Ech, próbowałem uruchomić fragment
test.cpp
kodu, który podałeś jako , poprzez gcc i clang oraz wiele poziomów optymalizacji:Więc to jest tam, gdzie robi się interesująco, wyraźnie pokazuje, że clang O0 build odczytuje liczby losowe, prawdopodobnie miejsce na stosie.
Szybko włączyłem IDA, żeby zobaczyć, co się dzieje:
Co teraz
bar::bar(bar *this)
robi?Hmm, nic. Musieliśmy uciekać się do montażu:
Więc tak, to po prostu nic, co zasadniczo robi konstruktor
this = this
. Ale wiemy, że w rzeczywistości ładuje losowe niezainicjowane adresy stosu i drukuje je.Co się stanie, jeśli jawnie podamy wartości dla dwóch struktur?
Uderz, brzęk, oopsie:
Podobny los również z g ++:
Oznacza to, że jest to faktycznie inicjalizacja bezpośrednia
bar b(0)
, a nie inicjalizacja zbiorcza.Dzieje się tak prawdopodobnie dlatego, że jeśli nie podasz jawnej implementacji konstruktora, może to być symbol zewnętrzny, na przykład:
Kompilator nie jest wystarczająco inteligentny, aby wydedukować to jako wywołanie bez operacji / wbudowane w niezoptymalizowanym etapie.
źródło