Menurut jawaban yang diterima (dan hanya) untuk pertanyaan Stack Overflow ini ,
Mendefinisikan konstruktor dengan
MyTest() = default;
sebagai gantinya akan menginisialisasi objek dengan nol.
Lalu mengapa berikut ini,
#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;
}
menghasilkan keluaran ini:
0 32766
Kedua konstruktor didefinisikan sebagai default? Baik? Dan untuk jenis POD, inisialisasi default adalah inisialisasi nol.
Dan menurut jawaban yang diterima untuk pertanyaan ini ,
Jika anggota POD tidak diinisialisasi dalam konstruktor atau melalui inisialisasi dalam kelas C ++ 11, ia diinisialisasi secara default.
Jawabannya sama terlepas dari tumpukan atau heap.
Di C ++ 98 (dan bukan setelahnya), new int () ditetapkan sebagai melakukan inisialisasi nol.
Meskipun mencoba untuk membungkus kepala saya (meskipun kecil ) di sekitar konstruktor default dan inisialisasi default , saya tidak dapat memberikan penjelasan.
sumber
bar
konstruktor adalah pengguna yang disediakan sedangkanfoo
konstruktor adalah yang default.a
adalah nol.b
tidak. Sepertinyaa
diinisialisasi.bar::bar()
terlihat dimain()
- definisi ini dapat didefinisikan dalam unit kompilasi terpisah dan melakukan sesuatu yang sangat tidak sepele sementaramain()
hanya deklarasi yang terlihat. Saya pikir Anda akan setuju bahwa perilaku ini tidak boleh berubah tergantung pada apakah Anda menempatkanbar::bar()
definisi di unit kompilasi terpisah atau tidak (bahkan jika seluruh situasi tidak intuitif).int a = 0;
apakah Anda ingin benar-benar eksplisit.Jawaban:
Masalahnya di sini cukup halus. Anda akan berpikir begitu
bar::bar() = default;
akan memberi Anda compiler yang dihasilkan konstruktor default, dan memang demikian, tetapi sekarang dianggap disediakan oleh pengguna. [dcl.fct.def.default] / 5 status:
penekanan milikku
Jadi kita dapat melihat bahwa karena Anda tidak melakukan default
bar()
saat pertama kali mendeklarasikannya, sekarang dianggap disediakan oleh pengguna. Karena itu [dcl.init] /8.2tidak lagi berlaku dan kami tidak menginisialisasi nilai
b
tetapi sebagai gantinya secara default menginisialisasi per [dcl.init] /8.1sumber
(*_*)
.... Jika untuk menggunakan konstruksi dasar bahasa, saya perlu membaca cetakan kecil draf bahasa, maka Haleluya! Tapi mungkin itu yang Anda katakan.bar::bar() = default
out of line sama dengan melakukanbar::bar(){}
inline.Perbedaan perilaku berasal dari fakta bahwa, menurut
[dcl.fct.def.default]/5
,bar::bar
disediakan pengguna di manafoo::foo
bukan 1 . Akibatnya,foo::foo
akan nilai-menginisialisasi anggotanya (artinya: nol-menginisialisasifoo::a
) tetapibar::bar
akan tetap tidak diinisialisasi 2 .1)
[dcl.fct.def.default]/5
2)
Dari jawaban Vittorio Romeo
sumber
Dari cppreference :
Dengan definisi ini,
foo
adalah agregat, sedangkanbar
bukan (memiliki konstruktor non-default yang disediakan pengguna).Oleh karena itu
foo
,T object {arg1, arg2, ...};
adalah sintaks untuk inisialisasi agregat.Oleh karena itu
a.a
adalah nilai yang diinisialisasi, yangint
berarti inisialisasi nol.Sebab
bar
,T object {};
di sisi lain adalah inisialisasi nilai (dari instance kelas, bukan inisialisasi nilai anggota!). Karena ini adalah tipe kelas dengan konstruktor default, konstruktor default dipanggil. Konstruktor default yang Anda tetapkan default menginisialisasi anggota (karena tidak memiliki inisialisasi anggota), yang dalam kasusint
(dengan penyimpanan non-statis) meninggalkanb.b
dengan nilai yang tidak pasti.Tidak. Ini salah.
NB Sepatah kata tentang percobaan Anda dan kesimpulan Anda: Melihat bahwa keluaran adalah nol tidak selalu berarti bahwa variabel diinisialisasi nol. Nol adalah angka yang sangat mungkin untuk nilai sampah.
Fakta bahwa nilainya sama beberapa kali tidak selalu berarti bahwa nilainya juga telah dimulai.
Fakta bahwa hasilnya sama dengan beberapa opsi kompilator tidak berarti variabel tersebut diinisialisasi. (Meskipun dalam beberapa kasus, mengubah versi standar dapat mengubah apakah itu sudah dimulai).
Tidak ada cara yang dijamin dalam C ++ untuk membuat nilai nilai yang tidak diinisialisasi tampak bukan nol.
Satu-satunya cara untuk mengetahui bahwa variabel diinisialisasi adalah dengan membandingkan program dengan aturan bahasa dan memverifikasi bahwa aturan tersebut menyatakan bahwa ia telah diinisialisasi. Dalam hal
a.a
ini memang dimulai.sumber
a
diinisialisasi. Saya berpikira
adalah inisialisasi default dan inisialisasi default untuk POD anggota adalah, inisialisasi nol. Apakaha
kemudian hanya untungnya selalu datang nol, tidak peduli berapa kali saya menjalankan program ini.Then how come a is initialized.
Karena ini adalah nilai yang diinisialisasi.I was thinking a is default initialized
Bukan itu.Yah, saya mencoba menjalankan cuplikan yang Anda berikan sebagai
test.cpp
, melalui gcc & clang dan beberapa tingkat pengoptimalan: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 ]
Jadi di situlah menjadi menarik, itu jelas menunjukkan clang O0 build membaca angka acak, mungkin ruang tumpukan.
Saya segera membuka IDA saya untuk melihat apa yang terjadi:
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; }
Sekarang, apa
bar::bar(bar *this)
fungsinya?void __fastcall bar::bar(bar *this) { ; }
Hmm, tidak ada. Kami harus menggunakan perakitan:
.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
Jadi ya, tidak apa-apa, yang pada dasarnya dilakukan oleh konstruktor
this = this
. Tapi kita tahu bahwa itu sebenarnya memuat alamat tumpukan acak yang tidak diinisialisasi dan mencetaknya.Bagaimana jika kita secara eksplisit memberikan nilai untuk kedua struct?
#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; }
Hit up clang, oopsie:
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 ]
Nasib serupa dengan g ++ juga:
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 ]
Jadi ini berarti inisialisasi langsung secara efektif
bar b(0)
, bukan inisialisasi agregat.Ini mungkin karena jika Anda tidak menyediakan implementasi konstruktor eksplisit, ini berpotensi menjadi simbol eksternal, misalnya:
bar::bar() { this.b = 1337; // whoa }
Kompilator tidak cukup pintar untuk menyimpulkan ini sebagai panggilan tanpa operasi / sebaris dalam tahap yang tidak dioptimalkan.
sumber