Meh, ich habe versucht, das von Ihnen bereitgestellte Snippet test.cppüber gcc & clang und mehrere Optimierungsstufen auszuführen:
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 ]
Hier wird es also interessant. Es zeigt deutlich, dass der Clang-O0-Build Zufallszahlen liest, vermutlich Stapelspeicher.
Ich drehte schnell meine IDA auf, um zu sehen, was passiert:
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;
}
Was bar::bar(bar *this)macht nun?
void __fastcall bar::bar(bar *this)
{
;
}
Hmm, nichts. Wir mussten auf die Montage zurückgreifen:
.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
Also ja, es ist einfach nichts, was der Konstruktor im Grunde tut this = this. Wir wissen jedoch, dass tatsächlich zufällige nicht initialisierte Stapeladressen geladen und gedruckt werden.
Was ist, wenn wir explizit Werte für die beiden Strukturen angeben?
#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 ]
Ähnliches Schicksal auch bei 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 ]
Dies bedeutet also, dass es sich effektiv um eine direkte Initialisierung handelt bar b(0), nicht um eine aggregierte Initialisierung.
Dies liegt wahrscheinlich daran, dass dies möglicherweise ein externes Symbol sein kann, wenn Sie keine explizite Konstruktorimplementierung bereitstellen, z.
bar::bar() {
this.b = 1337; // whoa
}
Der Compiler ist nicht klug genug, um dies in einer nicht optimierten Phase als No-Op / Inline-Aufruf abzuleiten.