Default, nilai dan nol inisialisasi berantakan

89

Saya sangat bingung tentang nilai- & default- & nol-inisialisasi. dan terutama ketika mereka menendang untuk standar yang berbeda C ++ 03 dan C ++ 11 (dan C ++ 14 ).

Saya mengutip dan mencoba untuk memperluas jawaban yang sangat bagus Value- / Default- / Zero- Init C ++ 98 dan C ++ 03 di sini untuk membuatnya lebih umum karena akan membantu banyak pengguna jika seseorang dapat membantu mengisi celah yang dibutuhkan untuk mendapatkan gambaran yang baik tentang apa yang terjadi dan kapan?

Wawasan lengkap dengan contoh singkatnya:

Terkadang memori yang dikembalikan oleh operator baru akan diinisialisasi, dan terkadang tidak tergantung pada apakah jenis yang Anda perbarui adalah POD (data lama biasa) , atau jika itu adalah kelas yang berisi anggota POD dan menggunakan compiler-generated default constructor.

  • Di C ++ 1998 ada 2 jenis inisialisasi: nol- dan default-inisialisasi
  • Dalam C ++ 2003 tipe ketiga inisialisasi, nilai-inisialisasi ditambahkan.
  • Di C ++ 2011 / C ++ 2014 hanya inisialisasi daftar yang ditambahkan dan aturan untuk nilai- / default- / zero-inisialisasi berubah sedikit.

Menganggap:

struct A { int m; };                     
struct B { ~B(); int m; };               
struct C { C() : m(){}; ~C(); int m; };  
struct D { D(){}; int m; };             
struct E { E() = default; int m;}; /** only possible in c++11/14 */  
struct F {F(); int m;};  F::F() = default; /** only possible in c++11/14 */

Dalam kompiler C ++ 98, hal berikut harus terjadi :

  • new A - nilai tak tentu ( Aadalah POD)
  • new A()- nol-menginisialisasi
  • new B - konstruksi default ( B::mtidak diinisialisasi, Bbukan POD)
  • new B()- konstruksi default ( B::mtidak diinisialisasi)
  • new C - konstruksi default ( C::mdiinisialisasi nol, Cnon-POD)
  • new C()- konstruksi default ( C::mdiinisialisasi nol)
  • new D - konstruksi default ( D::mtidak diinisialisasi, Dbukan POD)
  • new D()- konstruksi default? ( D::mtidak dimulai)

Dalam kompilator konforman C ++ 03, hal-hal akan bekerja seperti ini:

  • new A - nilai tak tentu ( Aadalah POD)
  • new A() - value-initialize A, yang merupakan inisialisasi nol karena ini adalah POD.
  • new B - default-inisialisasi ( B::mtidak diinisialisasi, Bnon-POD)
  • new B() - value-initializes Byang menginisialisasi nol semua bidang karena ctor defaultnya adalah kompilator yang dihasilkan sebagai lawan yang ditentukan pengguna.
  • new C - default-inisialisasi C, yang memanggil ctor default. ( C::mtidak diinisialisasi, Cbukan POD)
  • new C() - nilai-menginisialisasi C, yang memanggil ctor default. ( C::mdiinisialisasi nol)
  • new D - konstruksi default ( D::mtidak diinisialisasi, Dbukan POD)
  • new D() - nilai-menginisialisasi D? , yang memanggil ctor default ( D::mtidak diinisialisasi)

Nilai miring dan? adalah ketidakpastian, tolong bantu untuk memperbaikinya :-)

Dalam compiler konforman C ++ 11, semuanya akan bekerja seperti ini:

??? (tolong bantu jika saya mulai di sini tetap saja akan salah)

Dalam kompilator konforman C ++ 14, hal-hal harus bekerja seperti ini: ??? (tolong bantu jika saya mulai di sini tetap saja akan salah) (Draf berdasarkan jawaban)

  • new A - default-inisialisasi A, compiler gen. ctor, (leavs A::muninitialized) ( Aadalah POD)

  • new A() - nilai-menginisialisasi A, yang merupakan inisialisasi nol sejak 2. titik di [dcl.init] / 8

  • new B - default-inisialisasi B, kompilator gen. ctor, (leavs B::muninitialized) ( Bnon-POD)

  • new B() - value-initializes Byang menginisialisasi nol semua bidang karena ctor defaultnya adalah kompilator yang dihasilkan sebagai lawan yang ditentukan pengguna.

  • new C - default-inisialisasi C, yang memanggil ctor default. ( C::mtidak diinisialisasi, Cbukan POD)

  • new C() - nilai-menginisialisasi C, yang memanggil ctor default. ( C::mdiinisialisasi nol)

  • new D - default-inisialisasi D( D::mtidak diinisialisasi, Dbukan POD)

  • new D() - value-initializes D, yang memanggil ctor default ( D::mtidak diinisialisasi)

  • new E - default-inisialisasi E, yang memanggil comp. gen. ctor. ( E::mtidak diinisialisasi, E bukan POD)

  • new E() - nilai-menginisialisasi E, yang menginisialisasi nol Esejak 2 titik di [dcl.init] / 8 )

  • new F - default-inisialisasi F, yang memanggil comp. gen. ctor. ( F::mtidak diinisialisasi, Fbukan POD)

  • new F() - nilai-menginisialisasi F, yang menginisialisasi default F sejak 1. titik di [dcl.init] / 8 ( Ffungsi ctor disediakan pengguna jika dideklarasikan oleh pengguna dan tidak secara eksplisit default atau dihapus pada deklarasi pertamanya. Tautan )

Gabriel
sumber
ada penjelasan yang bagus tentangnya di sini: en.cppreference.com/w/cpp/language/default_constructor
Richard Hodges
1
Sejauh yang saya tahu, hanya ada perbedaan antara C ++ 98 dan C ++ 03 dalam contoh ini. Masalahnya tampaknya dijelaskan di N1161 (nanti ada revisi dari dokumen itu) dan CWG DR # 178 . Kata - kata perlu diubah di C ++ 11 karena fitur baru dan spesifikasi baru POD, dan itu berubah lagi di C ++ 14 karena cacat pada kata-kata C ++ 11, tetapi efek dalam kasus ini tidak berubah .
dyp
3
Meskipun membosankan, struct D { D() {}; int m; };mungkin ada baiknya untuk dimasukkan dalam daftar Anda.
Yakk - Adam Nevraumont

Jawaban:

24

C ++ 14 menentukan inisialisasi objek yang dibuat dengan newdi [expr.new] / 17 ([expr.new] / 15 di C ++ 11, dan catatan tersebut bukanlah catatan tetapi teks normatif saat itu):

Sebuah baru-ekspresi yang menciptakan objek tipe Tmenginisialisasi objek yang sebagai berikut:

  • Jika new-initializer dihilangkan, objek tersebut diinisialisasi secara default (8.5). [ Catatan: Jika tidak ada inisialisasi yang dilakukan, objek memiliki nilai tak tentu. - catatan akhir ]
  • Jika tidak, penginisialisasi baru ditafsirkan sesuai dengan aturan inisialisasi 8.5 untuk inisialisasi langsung .

Default-inisialisasi didefinisikan di [dcl.init] / 7 (/ 6 di C ++ 11, dan susunan kata itu sendiri memiliki efek yang sama):

Untuk menginisialisasi default objek bertipe Tberarti:

  • jika Tadalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9), konstruktor default (12.1) untuk Tdipanggil (dan inisialisasi tidak berbentuk jika Ttidak memiliki konstruktor default atau resolusi berlebih (13.3) menghasilkan ambiguitas atau fungsi yang dihapus atau tidak dapat diakses dari konteks inisialisasi);
  • jika Tadalah tipe array, setiap elemen diinisialisasi secara default ;
  • jika tidak, tidak ada inisialisasi yang dilakukan.

Jadi

  • new Ahanya menyebabkan Akonstruktor default dipanggil, yang tidak diinisialisasi m. Nilai tak tentu. Harus sama untuk new B.
  • new A() diinterpretasikan menurut [dcl.init] / 11 (/ 10 dalam C ++ 11):

    Objek yang penginisialisasinya adalah kumpulan tanda kurung kosong, yaitu (), harus diinisialisasi nilai.

    Dan sekarang pertimbangkan [dcl.init] / 8 (/ 7 di C ++ 11 †):

    Untuk menginisialisasi nilai objek bertipe Tberarti:

    • jika Tadalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9) tanpa konstruktor default (12.1) atau konstruktor default yang disediakan pengguna atau dihapus, maka objek tersebut diinisialisasi secara default;
    • jika Tadalah tipe kelas (mungkin memenuhi syarat cv) tanpa konstruktor default yang disediakan pengguna atau dihapus, maka objek tersebut diinisialisasi nol dan batasan semantik untuk inisialisasi default diperiksa, dan jika T memiliki konstruktor default non-sepele, objek tersebut diinisialisasi secara default;
    • jika Tadalah tipe array, maka setiap elemen diinisialisasi nilai;
    • jika tidak, objek tersebut diinisialisasi nol.

    Karenanya new A()akan menginisialisasi nol m. Dan ini harus setara dengan Adan B.

  • new Cdan new C()akan menginisialisasi objek secara default lagi, karena poin pertama dari kutipan terakhir berlaku (C memiliki konstruktor default yang disediakan pengguna!). Tapi, jelas, sekarang mdiinisialisasi di konstruktor dalam kedua kasus.


† Nah, paragraf ini memiliki kata-kata yang sedikit berbeda di C ++ 11, yang tidak mengubah hasil:

Untuk menginisialisasi nilai objek bertipe Tberarti:

  • jika Tadalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9) dengan konstruktor yang disediakan pengguna (12.1), maka konstruktor default untuk T dipanggil (dan inisialisasi tidak berbentuk jika T tidak memiliki konstruktor default yang dapat diakses);
  • jika Tadalah tipe kelas non-union (mungkin memenuhi syarat cv) tanpa konstruktor yang disediakan pengguna, maka objek tersebut diinisialisasi nol dan, jika Tkonstruktor default yang dideklarasikan secara implisit adalah non-sepele, konstruktor tersebut akan dipanggil.
  • jika Tadalah tipe array, maka setiap elemen diinisialisasi nilai;
  • jika tidak, objek tersebut diinisialisasi nol.
Columbo
sumber
ah jadi yang Anda bicarakan terutama tentang c ++ 14 dan referensi untuk c ++ 11 diberikan dalam tanda kurung
Gabriel
@Gabriel Benar. Maksud saya, C ++ 14 adalah standar terbaru, jadi itu kedepan.
Columbo
1
Hal yang menjengkelkan tentang mencoba melacak aturan inisialisasi di seluruh standar adalah bahwa banyak perubahan (kebanyakan? Semua?) Antara standar C ++ 14 dan C ++ 11 yang dipublikasikan terjadi melalui DR, dan begitu pula secara de facto C ++ 11 . Lalu ada juga pasca-C ++ 14 DR ...
TC
@ Columbo Saya masih tidak mengerti mengapa struct A { int m; }; struct C { C() : m(){}; int m; };menghasilkan hasil yang berbeda dan apa yang menyebabkan m di A diinisialisasi di tempat pertama. Saya telah membuka utas khusus untuk percobaan yang saya lakukan dan saya akan menghargai masukan Anda di sana untuk memperjelas masalah. Terima kasih stackoverflow.com/questions/45290121/…
darkThoughts
12

Jawaban berikut memperluas jawaban https://stackoverflow.com/a/620402/977038 yang akan berfungsi sebagai referensi untuk C ++ 98 dan C ++ 03

Mengutip jawabannya

  1. Di C ++ 1998 ada 2 jenis inisialisasi: nol dan default
  2. Di C ++ 2003 jenis inisialisasi ke-3, inisialisasi nilai ditambahkan.

C ++ 11 (Mengacu pada n3242)

Penginisialisasi

8.5 Penginisialisasi [dcl.init] menentukan bahwa variabel POD atau non POD dapat diinisialisasi baik sebagai brace-or-equal-initializer yang dapat berupa braced-init-list atau initializer-clause yang secara agregat disebut sebagai brace-or-equal- penginisialisasi atau menggunakan (daftar ekspresi) . Sebelum C ++ 11, hanya (daftar ekspresi) atau klausa penginisialisasi yang didukung meskipun klausa penginisialisasi lebih dibatasi daripada yang kita miliki di C ++ 11. Di C ++ 11, initializer-clause sekarang mendukung braced-init-list selain dari assignment-expressionseperti di C ++ 03. Tata bahasa berikut meringkas klausa baru yang didukung, di mana bagian yang dicetak tebal baru ditambahkan dalam standar C ++ 11.

penginisialisasi:
    brace-or-equal-initializer
    (daftar ekspresi)
brace-or-equal-initializer:
    = penginisialisasi-klausa
    braced-init-list
penginisialisasi-klausa:
    penugasan-ekspresi
    braced-init-list
penginisialisasi-daftar:
    penginisialisasi-klausa ...
    pilih daftar penginisialisasi, klausa penginisialisasi ... pilih **
braced-init-list:
    {daftar penginisialisasi, pilih}
    {}

Inisialisasi

Seperti C ++ 03, C ++ 11 masih mendukung tiga bentuk inisialisasi


Catatan

Bagian yang disorot dalam huruf tebal telah ditambahkan di C ++ 11 dan bagian yang dicoret telah dihapus dari C ++ 11.

  1. Jenis Penginisialisasi: 8.5.5 [dcl.init] _zero-initialize_

Dilakukan dalam kasus berikut

  • Objek dengan durasi penyimpanan statis atau utas diinisialisasi nol
  • Jika ada lebih sedikit penginisialisasi daripada ada elemen larik, setiap elemen yang tidak diinisialisasi secara eksplisit harus diinisialisasi nol
  • Selama value-initialize , jika T adalah tipe kelas non-union (mungkin memenuhi syarat cv) tanpa konstruktor yang disediakan pengguna, maka objek tersebut diinisialisasi nol.

Untuk menginisialisasi nol objek atau referensi tipe T berarti:

  • jika T adalah tipe skalar (3.9), objek disetel ke nilai 0 (nol), diambil sebagai ekspresi konstanta integral , diubah ke T;
  • jika T adalah tipe kelas non-union (mungkin berkualifikasi cv) , setiap anggota data non-statis dan setiap subobjek kelas dasar diinisialisasi nol dan padding diinisialisasi ke bit nol;
  • jika T adalah tipe gabungan (mungkin memenuhi syarat cv) , anggota data bernama non-statis pertama dari objek itu diinisialisasi nol dan padding diinisialisasi ke bit nol;
  • jika T adalah tipe array, setiap elemen diinisialisasi nol;
  • jika T adalah tipe referensi, tidak ada inisialisasi yang dilakukan.

2. Jenis Penginisialisasi: 8.5.6 [dcl.init] _default-initialize_

Dilakukan dalam kasus berikut

  • Jika new-initializer dihilangkan, objek tersebut akan diinisialisasi secara default; jika tidak ada inisialisasi yang dilakukan, objek tersebut memiliki nilai tak tentu.
  • Jika tidak ada penginisialisasi yang ditentukan untuk suatu objek, objek tersebut diinisialisasi secara default, kecuali untuk Objek dengan durasi penyimpanan statis atau utas
  • Ketika kelas dasar atau anggota data non-statis tidak disebutkan dalam daftar penginisialisasi konstruktor dan konstruktor itu dipanggil.

Untuk menginisialisasi default objek tipe T berarti:

  • jika T adalah jenis kelas non-POD (mungkin memenuhi syarat cv) (Klausul 9), konstruktor default untuk T dipanggil (dan inisialisasi tidak berbentuk jika T tidak memiliki konstruktor default yang dapat diakses);
  • jika T adalah tipe array, setiap elemen diinisialisasi secara default;
  • jika tidak, tidak ada inisialisasi yang dilakukan.

Catatan Hingga C ++ 11, hanya jenis kelas non-POD dengan durasi penyimpanan otomatis yang dianggap inisialisasi default ketika tidak ada penginisialisasi yang digunakan.


3. Jenis Penginisialisasi: 8.5.7 [dcl.init] _value-initialize_

  1. Ketika sebuah objek (sementara tanpa nama, variabel bernama, durasi penyimpanan dinamis atau anggota data non-statis) yang penginisialisasinya adalah kumpulan tanda kurung kosong, yaitu () atau kurung kurawal {}

Untuk menginisialisasi nilai objek tipe T berarti:

  • jika T adalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9) dengan konstruktor yang disediakan pengguna (12.1), maka konstruktor default untuk T dipanggil (dan inisialisasi menjadi tidak benar jika T tidak memiliki konstruktor default yang dapat diakses) ;
  • jika T adalah tipe kelas non-union (mungkin berkualifikasi cv) tanpa konstruktor yang disediakan pengguna, maka setiap anggota data non-statis dan komponen kelas dasar T diinisialisasi nilai; maka objek tersebut diinisialisasi nol dan, jika konstruktor default T yang dideklarasikan secara implisit adalah non-trivial, konstruktor tersebut akan dipanggil.
  • jika T adalah tipe array, maka setiap elemen diinisialisasi nilai;
  • jika tidak, objek tersebut diinisialisasi nol.

Jadi untuk meringkas

Catatan Kutipan yang relevan dari standar ditandai dengan huruf tebal

  • baru A: default-inisialisasi (membiarkan A :: m tidak diinisialisasi)
  • new A (): Zero-initialize A, karena kandidat yang diinisialisasi nilai tidak memiliki konstruktor default yang disediakan pengguna atau dihapus. jika T adalah tipe kelas non-union (mungkin berkualifikasi cv) tanpa konstruktor yang disediakan pengguna, maka objek tersebut diinisialisasi nol dan, jika konstruktor default yang dideklarasikan secara implisit T adalah non-trivial, konstruktor tersebut akan dipanggil.
  • baru B: default-inisialisasi (membiarkan B :: m tidak diinisialisasi)
  • new B (): nilai-menginisialisasi B yang menginisialisasi nol semua bidang; jika T adalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9) dengan konstruktor yang disediakan pengguna (12.1), maka konstruktor default untuk T dipanggil
  • new C: default-inisialisasi C, yang memanggil ctor default. jika T adalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9), konstruktor default untuk T dipanggil , Selain itu Jika penginisialisasi baru dihilangkan, objek diinisialisasi default
  • new C (): menginisialisasi nilai C, yang memanggil ctor default. jika T adalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9) dengan konstruktor yang disediakan pengguna (12.1), maka konstruktor default untuk T dipanggil. Selain itu, objek yang penginisialisasinya adalah kumpulan tanda kurung kosong, yaitu (), harus diinisialisasi nilai
Abhijit
sumber
0

Saya dapat mengonfirmasi bahwa di C ++ 11, semua yang disebutkan dalam pertanyaan di bawah C ++ 14 sudah benar, setidaknya sesuai implementasi kompiler.

Untuk memverifikasi ini, saya menambahkan kode berikut ke rangkaian pengujian saya . Saya menguji dengan -std=c++11 -O3GCC 7.4.0, GCC 5.4.0, Clang 10.0.1, dan VS 2017, dan semua pengujian di bawah ini berhasil.

#include <gtest/gtest.h>
#include <memory>

struct A { int m;                    };
struct B { int m;            ~B(){}; };
struct C { int m; C():m(){}; ~C(){}; };
struct D { int m; D(){};             };
struct E { int m; E() = default;     };
struct F { int m; F();               }; F::F() = default;

// We use this macro to fill stack memory with something else than 0.
// Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but
// pass in practice, and help illustrate that `a.m` is indeed not initialized
// to zero. Note that we initially tried the more aggressive test
// EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to
// 42, but was still equal to some garbage value, not zero).
//
#define FILL { int m = 42; EXPECT_EQ(m, 42); }

// We use this macro to fill heap memory with something else than 0, before
// doing a placement new at that same exact location. Subsequent calls to
// EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice,
// and help illustrate that `a->m` is indeed not initialized to zero.
//
#define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42)

TEST(TestZero, StackDefaultInitialization)
{
    { FILL; A a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; B a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; C a; EXPECT_EQ(a.m, 0); }
    { FILL; D a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; F a; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackValueInitialization)
{
    { FILL; A a = A(); EXPECT_EQ(a.m, 0); }
    { FILL; B a = B(); EXPECT_EQ(a.m, 0); }
    { FILL; C a = C(); EXPECT_EQ(a.m, 0); }
    { FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a = E(); EXPECT_EQ(a.m, 0); }
    { FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackListInitialization)
{
    { FILL; A a{}; EXPECT_EQ(a.m, 0); }
    { FILL; B a{}; EXPECT_EQ(a.m, 0); }
    { FILL; C a{}; EXPECT_EQ(a.m, 0); }
    { FILL; D a{}; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a{}; EXPECT_EQ(a.m, 0); }
    { FILL; F a{}; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, HeapDefaultInitialization)
{
    { FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapValueInitialization)
{
    { FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapListInitialization)
{
    { FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB
}

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Tempat-tempat UB!yang disebutkan adalah perilaku yang tidak ditentukan, dan perilaku sebenarnya kemungkinan besar bergantung pada banyak faktor ( a.mmungkin sama dengan 42, 0, atau sampah lainnya). Tempat-tempat ~UByang disebutkan juga merupakan perilaku yang tidak ditentukan dalam teori, tetapi dalam praktiknya, karena penggunaan penempatan baru, sangat tidak mungkin daripada a->makan sama dengan apa pun selain 42.

Boris Dalstein
sumber