C ++ sıfır başlatma - Bu programdaki `b` neden başlatılmadı, ancak` a` başlatıldı?


135

Bu Yığın Taşması sorusunun kabul edilen (ve yalnızca) cevabına göre ,

Yapıcı ile tanımlanması

MyTest() = default;

bunun yerine nesneyi sıfır başlatacaktır.

O zaman aşağıdakiler neden yapar,

#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;
}

bu çıktıyı üretmek:

0 32766

Tanımlanan her iki kurucu varsayılan? Sağ? POD türleri için varsayılan başlatma sıfır başlatmadır.

Ve bu soru için kabul edilen cevaba göre ,

  1. Bir POD üyesi yapıcıda veya C ++ 11 sınıf içi başlatma yoluyla başlatılmazsa, varsayılan olarak başlatılır.

  2. Cevap yığın veya yığından bağımsız olarak aynıdır.

  3. C ++ 98'de (ve sonrasında değil), sıfır başlatma gerçekleştirirken yeni int () belirtildi.

( Küçük de olsa ) kafamı varsayılan kurucuların ve varsayılan başlatmanın etrafına sarmaya çalışmama rağmen, bir açıklama yapamadım.


3
İlginç bir şekilde, b: main.cpp: 18: 34: uyarı: 'b.bar::b' için bir uyarı bile alıyorum [-Wuninitialized] coliru.stacked-crooked.com/a/d1b08a4d6fb4ca7e
tkausl

8
baradlı kullanıcının fooyapıcısı varsayılan olarak yapılandırılır.
Jarod42

2
@ PeteBecker, bunu anlıyorum. Bir şekilde RAM'imi nasıl bir şekilde sallayabilirim ki orada sıfır olsaydı, şimdi başka bir şey olmalı. ;) ps Programı bir düzine kez çalıştırdım. Büyük bir program değil. Çalıştırabilir ve sisteminizde test edebilirsiniz. asıfırdır. bdeğil. Görünüşe agöre başlatıldı.
Duck Dodgers

2
@JoeyMallone "Nasıl kullanıcı tarafından sağlanır" ile ilgili olarak: Tanımı tanımlamanın bar::bar()görünür olduğuna dair bir garanti yoktur main()- ayrı bir derleme biriminde tanımlanabilir ve main()yalnızca bildirim görünürken çok önemsiz bir şey yapabilir . Bence bu davranışın bar::bar()tanımını ayrı bir derleme birimine yerleştirip yerleştirmemenize bağlı olarak değişmeyeceğini kabul edersiniz (bütün durum kasıtsız olsa bile).
Max Langhof

2
@balki Yoksa int a = 0;gerçekten açık olmak ister misiniz?
NathanOliver

Yanıtlar:


109

Buradaki konu oldukça incedir. Bunu düşünürdün

bar::bar() = default;

derleyici tarafından oluşturulan varsayılan yapıcı verir ve yapar, ancak şimdi kullanıcı tarafından sağlanan kabul edilir. [dcl.fct.def.default] / 5 durum:

Açık olarak varsayılan işlevler ve örtük olarak bildirilen işlevler topluca varsayılan işlevler olarak adlandırılır ve uygulama onlar için örtülü tanımlamalar sağlar ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign ]). Bir işlev, kullanıcı tarafından bildirilmişse ve ilk bildiriminde açıkça varsayılan olarak veya silinmemişse kullanıcı tarafından sağlanır.Kullanıcı tarafından sağlanan açıkça varsayılan bir işlev (yani, ilk bildiriminden sonra açıkça varsayılan olan), açıkça varsayılan olduğu noktada tanımlanır; böyle bir işlev örtük olarak silinmiş olarak tanımlanırsa, program kötü biçimlendirilir. [Not: Bir işlevi ilk bildiriminden sonra varsayılan olarak varsayılan olarak bildirmek, gelişen kod tabanına istikrarlı bir ikili arabirim sağlarken verimli yürütme ve özlü tanımlama sağlayabilir. - son not]

benimkini vurgula

Böylece bar(), ilk kez bildirdiğinizde varsayılan olmadığınızdan, artık kullanıcı tarafından sağlanan olarak kabul edildiğini görebiliriz. Bundan dolayı [dcl.init] /8.2

T, kullanıcı tarafından sağlanan veya silinmiş bir varsayılan kurucu içermeyen (muhtemelen cv-nitelikli) bir sınıf tipiyse, nesne sıfır başlatılır ve varsayılan başlatma için semantik kısıtlamalar kontrol edilir ve T önemsiz olmayan bir varsayılan kurucuya sahipse , nesne varsayılan olarak başlatılır;

artık geçerli değil ve biz değer başlatma bdeğil, bunun yerine [dcl.init] /8.1

T, varsayılan kurucu ([class.default.ctor]) veya kullanıcı tarafından sağlanan veya silinmiş varsayılan bir kurucu olmayan (muhtemelen cv nitelikli) bir sınıf tipidir ([sınıf]), nesne varsayılan olarak başlatılır ;


52
Demek istediğim (*_*).... Dilin temel yapılarını bile kullanabilmek için, dil taslağının ince baskısını okumalıyım, o zaman Hallelujah! Ama muhtemelen söylediğiniz gibi görünüyor.
Ördek Dodgers

12
@balki Evet, çizgisiz bar::bar() = defaultyapmak bar::bar(){}satır içi yapmakla aynıdır .
NathanOliver

15
@JoeyMallone Evet, C ++ oldukça karmaşık olabilir. Bunun sebebinin ne olduğundan emin değilim.
NathanOliver

3
Önceden bir bildirim varsa, varsayılan anahtar kelimeye sahip bir sonraki tanım üyeleri sıfırlamaz. Sağ? Doğru. Burada olan budur.
NathanOliver

6
Nedeni tam olarak teklifinizde: hat dışı bir varsayılanın noktası, "gelişen bir kod tabanına istikrarlı bir ikili arabirim sağlarken etkili bir yürütme ve özlü tanım sağlamak", başka bir deyişle, ABI'yi bozmadan gerekirse kullanıcı tarafından yazılmış bir kuruluş. Hat dışı tanımın örtülü olarak satır içi olmadığını ve bu nedenle varsayılan olarak yalnızca bir TU'da görünebileceğini unutmayın; sınıf tanımını tek başına gören başka bir TU, açıkça varsayılan olarak tanımlanıp tanımlanmadığını bilmenin bir yolu yoktur.
TC

25

Davranış farkı göre olmasından kaynaklanır [dcl.fct.def.default]/5, bar::barbir kullanıcı tarafından sağlanan burada foo::foodeğildir 1 . Sonuç olarak, üyelerini değer-foo::foo başlatacak (yani: sıfır- başlatacak ) ama başlamamış kalacaktır 2 . foo::abar::bar


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

Bir işlev, kullanıcı tarafından bildirilmişse ve ilk bildiriminde açıkça varsayılan olarak veya silinmemişse kullanıcı tarafından sağlanır .

2)

Gönderen [dcl.init # 6] :

T türündeki bir nesneyi değer başlatmak için:

  • T varsayılan kurucu ([class.ctor]) veya kullanıcı tarafından sağlanan veya silinmiş varsayılan kurucu olmayan (muhtemelen cv-nitelikli) bir sınıf tipiyse, nesne varsayılan olarak başlatılır;

  • T, kullanıcı tarafından sağlanan veya silinmiş bir varsayılan kurucu içermeyen (muhtemelen cv-nitelikli) bir sınıf tipiyse , nesne sıfır başlatılır ve varsayılan başlatma için semantik kısıtlamalar kontrol edilir ve T önemsiz olmayan bir varsayılan kurucuya sahipse , nesne varsayılan olarak başlatılır;

  • ...

Gönderen [dcl.init.list] :

Bir nesnenin listeye başlatılması veya T tip referansı aşağıdaki gibi tanımlanır:

  • ...

  • Aksi takdirde, başlatıcı listesinde öğe yoksa ve T varsayılan kurucuya sahip bir sınıf türüyse, nesne değerle başlatılır.

Gönderen Vittorio Romeo'nun cevap


10

Gönderen cppreference :

Toplam başlatma, toplamları başlatır. Bir liste başlatma şeklidir.

Toplama aşağıdaki türlerden biridir:

[Kesik]

  • sınıf türü [snip],

    • [snip] (farklı standart sürümler için varyasyonlar vardır)

    • kullanıcı tarafından sağlanan, devralınan veya açık kuruculara (açıkça varsayılan veya silinmiş kuruculara izin verilmez)

    • [snip] (her iki sınıfa da uygulanan daha fazla kural vardır)

Bu tanım göz önüne alındığında foo, bir toplama bardeğildir , ancak değildir (kullanıcı tarafından sağlanan, varsayılan olmayan yapıcıya sahiptir).

Bu nedenle foo, T object {arg1, arg2, ...};toplam başlatma için sözdizimidir.

Toplam başlatmanın etkileri şunlardır:

  • [snip] (bu durumla ilgili olmayan bazı ayrıntılar)

  • Başlatıcı cümleciklerinin sayısı üye sayısından azsa veya başlatıcı listesi tamamen boşsa, kalan üyeler değerle başlatılır .

Bu nedenle a.a, intsıfır başlatma anlamına gelen değer başlatılır.

İçin bar, T object {};diğer taraftan değer başlatılması olduğunu (sınıf örneği değil, üyelerin değeri başlatılması ait!). Varsayılan yapıcıya sahip bir sınıf türü olduğundan, varsayılan yapıcı çağrılır. Varsayılan olarak tanımladığınız varsayılan kurucu, üyeleri ( intstatik olmayan depolama ile) b.bbelirsiz bir değerle ayrıldığında bırakır ( üye başlatıcıları olmamasından dolayı ) .

Kapsül türleri için varsayılan başlatma sıfır başlatmadır.

Hayır. Bu yanlış.


PS: Denemeniz ve sonucunuzla ilgili bir kelime: Çıktının sıfır olduğunu görmek, değişkenin sıfır başlatıldığı anlamına gelmez. Sıfır değeri, bir çöp değeri için mükemmel bir şekilde mümkündür.

bunun için programı yayınlamadan önce belki 5 ~ 6 kez çalıştırdım ve şimdi yaklaşık 10 kez, a her zaman sıfırdır. b biraz değişir.

Değerin birçok kez aynı olması, her zaman da başlatılmış olduğu anlamına gelmez.

Ayrıca set (CMAKE_CXX_STANDARD 14) ile de denedim. Sonuç aynıydı.

Birden çok derleyici seçeneği ile sonucun aynı olması, değişkenin başlatıldığı anlamına gelmez. (Bazı durumlarda, standart sürümün değiştirilmesi, başlatılıp başlatılmadığını değiştirebilir).

RAM'imi bir şekilde nasıl sallayabilirim ki orada sıfır olsaydı, şimdi başka bir şey olmalı

Sıfırlanmamış değer değerinin sıfır dışında görünmesini sağlamak için C ++ 'da garanti edilen bir yol yoktur.

Bir değişkenin başlatıldığını bilmenin tek yolu, programı dilin kuralları ile karşılaştırmak ve kuralların başlatıldığını söylediğini doğrulamaktır. Bu durumda a.agerçekten başlatılmıştır.


Msgstr "Varsayılan olarak tanımladığınız varsayılan kurucu, üyeleri başlatır (üye başlatıcılara sahip olmamasından dolayı), int durumunda belirsiz bir değer bırakır." -> ha! "Kapsül türleri için varsayılan başlatma sıfır başlatmadır." yoksa yanılıyor muyum?
Duck Dodgers

2
@JoeyMallone POD türlerinin varsayılan başlatma işlemi başlatma değildir.
NathanOliver

@NathanOliver, O zaman daha da kafam karıştı. Sonra nasıl abaşlanır. aBir üye POD için varsayılan başlatma ve sıfır başlatma olduğunu varsayılan düşünüyordum . O azaman neyse ki her zaman sıfıra geliyor, bu programı kaç kez çalıştırsam da önemli değil.
Duck Dodgers

@JoeyMallone Then how come a is initialized.Çünkü değer başlatıldı. I was thinking a is default initializedO değil.
eerorika

3
@JoeyMallone Endişelenme. C ++ 'da başlatma dışında bir kitap yapabilirsiniz. Bir şansınız varsa youtube'da CppCon, en hayal kırıklığı yaratan (ne kadar kötü olduğunu gösterdiği gibi) youtube.com/watch?v=7DTlWPgX6zs
NathanOliver

0

Meh, verdiğiniz pasajı test.cppgcc & clang ve çoklu optimizasyon seviyeleri ile çalıştırmayı denedim :

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 ]

Bu ilginç hale geliyor, açıkça o clang O0 inşa rastgele sayıları okuyor, muhtemelen yığın alanı gösterir.

Neler olduğunu görmek için IDA'mı çabucak açtım:

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;
}

Şimdi ne bar::bar(bar *this)yapıyor?

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

Hmm, hiçbir şey. Meclisi kullanarak başvurmak zorunda kaldık:

.text:00000000000011D0                               ; __int64 __fastcall bar::bar(bar *__hidden this)
.text:00000000000011D0                                               public _ZN3barC2Ev
.text:00000000000011D0                               _ZN3barC2Ev     proc near               ; CODE XREF: main+20p
.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

Yani evet, sadece, hiçbir şey, kurucunun temelde yaptığı şey this = this. Ancak aslında rastgele başlatılmamış yığın adresleri yüklediğini ve yazdıracağını biliyoruz.

İki yapı için açıkça değerler verirsek ne olur?

#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;
}

Clang vur, 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 ]

G ++ ile benzer kader:

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 ]

Bu bar b(0), toplam başlatma değil, etkili bir şekilde doğrudan başlatma anlamına gelir .

Bunun nedeni, açık bir kurucu uygulaması sağlamazsanız bunun potansiyel olarak harici bir sembol olabileceğidir, örneğin:

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

Derleyici, optimize edilmemiş bir aşamada bunu no-op / inline çağrı olarak çıkaracak kadar akıllı değildir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.