C++ zero initialization - Why is `b` in this program uninitialized, but `a` is initialized?
According to the accepted (and only) answer for this Stack Overflow question,
Defining the constructor with
MyTest() = default;
will instead zero-initialize the object.
Then why does the following,
#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;
}
produce this output:
0 32766
Both constructors defined are default? Right? And for POD types, the default initialization is zero-initialization.
And according to the accepted answer for this question,
If a POD member is not initialized in the constructor nor via C++11
in-class initialization, it is default-initialized.
The answer is the same regardless of stack or heap.
In C++98 (and not afterward), new int() was specified as performing
zero initialization.
Despite trying to wrap my (albeit tiny) head around default constructors and default initialization, I couldn't come up with an explanation.
c++ initialization language-lawyer
|
show 15 more comments
According to the accepted (and only) answer for this Stack Overflow question,
Defining the constructor with
MyTest() = default;
will instead zero-initialize the object.
Then why does the following,
#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;
}
produce this output:
0 32766
Both constructors defined are default? Right? And for POD types, the default initialization is zero-initialization.
And according to the accepted answer for this question,
If a POD member is not initialized in the constructor nor via C++11
in-class initialization, it is default-initialized.
The answer is the same regardless of stack or heap.
In C++98 (and not afterward), new int() was specified as performing
zero initialization.
Despite trying to wrap my (albeit tiny) head around default constructors and default initialization, I couldn't come up with an explanation.
c++ initialization language-lawyer
3
Interestingly, I even get a warning for b: main.cpp:18:34: warning: 'b.bar::b' is used uninitialized in this function [-Wuninitialized] coliru.stacked-crooked.com/a/d1b08a4d6fb4ca7e
– tkausl
Jan 24 at 15:32
8
bar
's constructor is user provided whereasfoo
's constructor is the defaulted one.
– Jarod42
Jan 24 at 15:33
2
@PeteBecker, I understand that. How could I somehow shake my RAM a little so that if there was zero there, it should now be something else. ;) p.s. I ran the program a dozen times. It is not a big program. You could run it and test it on your system.a
is zero.b
is not. Seemsa
is initialized.
– Joey Mallone
Jan 24 at 15:41
2
@JoeyMallone Regarding "how is it user-provided": There is no guarantee that the definition ofbar::bar()
is visible inmain()
- it might be defined in a separate compilation unit and do something very non-trivial while inmain()
only the declaration is visible. I think you'll agree that this behavior shouldn't change depending on whether you placebar::bar()
's definition in a separate compilation unit or not (even if the whole situation is unintuitive).
– Max Langhof
Jan 24 at 15:47
2
@balki Orint a = 0;
is you want to be really explicit.
– NathanOliver
Jan 24 at 16:27
|
show 15 more comments
According to the accepted (and only) answer for this Stack Overflow question,
Defining the constructor with
MyTest() = default;
will instead zero-initialize the object.
Then why does the following,
#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;
}
produce this output:
0 32766
Both constructors defined are default? Right? And for POD types, the default initialization is zero-initialization.
And according to the accepted answer for this question,
If a POD member is not initialized in the constructor nor via C++11
in-class initialization, it is default-initialized.
The answer is the same regardless of stack or heap.
In C++98 (and not afterward), new int() was specified as performing
zero initialization.
Despite trying to wrap my (albeit tiny) head around default constructors and default initialization, I couldn't come up with an explanation.
c++ initialization language-lawyer
According to the accepted (and only) answer for this Stack Overflow question,
Defining the constructor with
MyTest() = default;
will instead zero-initialize the object.
Then why does the following,
#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;
}
produce this output:
0 32766
Both constructors defined are default? Right? And for POD types, the default initialization is zero-initialization.
And according to the accepted answer for this question,
If a POD member is not initialized in the constructor nor via C++11
in-class initialization, it is default-initialized.
The answer is the same regardless of stack or heap.
In C++98 (and not afterward), new int() was specified as performing
zero initialization.
Despite trying to wrap my (albeit tiny) head around default constructors and default initialization, I couldn't come up with an explanation.
c++ initialization language-lawyer
c++ initialization language-lawyer
edited Feb 3 at 13:20
Yvette Colomb♦
20.3k1470112
20.3k1470112
asked Jan 24 at 15:29
Joey MalloneJoey Mallone
2,23641833
2,23641833
3
Interestingly, I even get a warning for b: main.cpp:18:34: warning: 'b.bar::b' is used uninitialized in this function [-Wuninitialized] coliru.stacked-crooked.com/a/d1b08a4d6fb4ca7e
– tkausl
Jan 24 at 15:32
8
bar
's constructor is user provided whereasfoo
's constructor is the defaulted one.
– Jarod42
Jan 24 at 15:33
2
@PeteBecker, I understand that. How could I somehow shake my RAM a little so that if there was zero there, it should now be something else. ;) p.s. I ran the program a dozen times. It is not a big program. You could run it and test it on your system.a
is zero.b
is not. Seemsa
is initialized.
– Joey Mallone
Jan 24 at 15:41
2
@JoeyMallone Regarding "how is it user-provided": There is no guarantee that the definition ofbar::bar()
is visible inmain()
- it might be defined in a separate compilation unit and do something very non-trivial while inmain()
only the declaration is visible. I think you'll agree that this behavior shouldn't change depending on whether you placebar::bar()
's definition in a separate compilation unit or not (even if the whole situation is unintuitive).
– Max Langhof
Jan 24 at 15:47
2
@balki Orint a = 0;
is you want to be really explicit.
– NathanOliver
Jan 24 at 16:27
|
show 15 more comments
3
Interestingly, I even get a warning for b: main.cpp:18:34: warning: 'b.bar::b' is used uninitialized in this function [-Wuninitialized] coliru.stacked-crooked.com/a/d1b08a4d6fb4ca7e
– tkausl
Jan 24 at 15:32
8
bar
's constructor is user provided whereasfoo
's constructor is the defaulted one.
– Jarod42
Jan 24 at 15:33
2
@PeteBecker, I understand that. How could I somehow shake my RAM a little so that if there was zero there, it should now be something else. ;) p.s. I ran the program a dozen times. It is not a big program. You could run it and test it on your system.a
is zero.b
is not. Seemsa
is initialized.
– Joey Mallone
Jan 24 at 15:41
2
@JoeyMallone Regarding "how is it user-provided": There is no guarantee that the definition ofbar::bar()
is visible inmain()
- it might be defined in a separate compilation unit and do something very non-trivial while inmain()
only the declaration is visible. I think you'll agree that this behavior shouldn't change depending on whether you placebar::bar()
's definition in a separate compilation unit or not (even if the whole situation is unintuitive).
– Max Langhof
Jan 24 at 15:47
2
@balki Orint a = 0;
is you want to be really explicit.
– NathanOliver
Jan 24 at 16:27
3
3
Interestingly, I even get a warning for b: main.cpp:18:34: warning: 'b.bar::b' is used uninitialized in this function [-Wuninitialized] coliru.stacked-crooked.com/a/d1b08a4d6fb4ca7e
– tkausl
Jan 24 at 15:32
Interestingly, I even get a warning for b: main.cpp:18:34: warning: 'b.bar::b' is used uninitialized in this function [-Wuninitialized] coliru.stacked-crooked.com/a/d1b08a4d6fb4ca7e
– tkausl
Jan 24 at 15:32
8
8
bar
's constructor is user provided whereas foo
's constructor is the defaulted one.– Jarod42
Jan 24 at 15:33
bar
's constructor is user provided whereas foo
's constructor is the defaulted one.– Jarod42
Jan 24 at 15:33
2
2
@PeteBecker, I understand that. How could I somehow shake my RAM a little so that if there was zero there, it should now be something else. ;) p.s. I ran the program a dozen times. It is not a big program. You could run it and test it on your system.
a
is zero. b
is not. Seems a
is initialized.– Joey Mallone
Jan 24 at 15:41
@PeteBecker, I understand that. How could I somehow shake my RAM a little so that if there was zero there, it should now be something else. ;) p.s. I ran the program a dozen times. It is not a big program. You could run it and test it on your system.
a
is zero. b
is not. Seems a
is initialized.– Joey Mallone
Jan 24 at 15:41
2
2
@JoeyMallone Regarding "how is it user-provided": There is no guarantee that the definition of
bar::bar()
is visible in main()
- it might be defined in a separate compilation unit and do something very non-trivial while in main()
only the declaration is visible. I think you'll agree that this behavior shouldn't change depending on whether you place bar::bar()
's definition in a separate compilation unit or not (even if the whole situation is unintuitive).– Max Langhof
Jan 24 at 15:47
@JoeyMallone Regarding "how is it user-provided": There is no guarantee that the definition of
bar::bar()
is visible in main()
- it might be defined in a separate compilation unit and do something very non-trivial while in main()
only the declaration is visible. I think you'll agree that this behavior shouldn't change depending on whether you place bar::bar()
's definition in a separate compilation unit or not (even if the whole situation is unintuitive).– Max Langhof
Jan 24 at 15:47
2
2
@balki Or
int a = 0;
is you want to be really explicit.– NathanOliver
Jan 24 at 16:27
@balki Or
int a = 0;
is you want to be really explicit.– NathanOliver
Jan 24 at 16:27
|
show 15 more comments
4 Answers
4
active
oldest
votes
The issue here is pretty subtle. You would think that
bar::bar() = default;
would give you a compiler generated default constructor, and it does, but it is now considered user provided. [dcl.fct.def.default]/5 states:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
emphasis mine
So we can see that since you did not default bar()
when you first declared it, it is now considered user provided. Because of that [dcl.init]/8.2
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
no longer applies and we are not value initializing b
but instead default initializing it per [dcl.init]/8.1
if T is a (possibly cv-qualified) class type ([class]) with either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
51
I mean(*_*)
.... If to even use the basic constructs of the language, I need to read the fine print of the language draft, then Hallelujah! But it probably seems to be what you say.
– Joey Mallone
Jan 24 at 15:46
11
@balki Yes, doingbar::bar() = default
out of line is the same as doingbar::bar(){}
inline.
– NathanOliver
Jan 24 at 15:47
14
@JoeyMallone Yeah, C++ can be pretty complicated. I'm not sure what the reason for this is.
– NathanOliver
Jan 24 at 15:49
3
If there is a previous declaration, then a subsequent definition with the default keyword will NOT zero initialize the members. Right? This is correct. It is what is happening here.
– NathanOliver
Jan 24 at 16:16
6
The reason is right there in your quote: the point of an out-of-line default is to "provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base", in other words, enable you to switch to a user-written body later if necessary without breaking ABI. Note that the out-of-line definition is not implicitly inline and so can only appear in one TU by default; another TU seeing the class definition alone has no way to know if it's explicitly defined as defaulted.
– T.C.
Jan 24 at 20:33
|
show 14 more comments
The difference in behaviour comes from the fact that, according to [dcl.fct.def.default]/5
, bar::bar
is user-provided where foo::foo
is not1. As a consequence, foo::foo
will value-initialize its members (meaning: zero-initialize foo::a
) but bar::bar
will stay uninitialized2.
1)[dcl.fct.def.default]/5
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
2)
From [dcl.init#6]:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
...
From [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
From Vittorio Romeo's answer
add a comment |
From cppreference:
Aggregate initialization initializes aggregates. It is a form of list-initialization.
An aggregate is one of the following types:
[snip]
class type [snip], that has
[snip] (there are variations for different standard versions)
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed)
[snip] (there are more rules, which apply to both classes)
Given this definition, foo
is an aggregate, while bar
is not (it has user-provided, non-defaulted constructor).
Therefore for foo
, T object {arg1, arg2, ...};
is syntax for aggregate initialisation.
The effects of aggregate initialization are:
[snip] (some details irrelevant to this case)
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized.
Therefore a.a
is value initialised, which for int
means zero initialisation.
For bar
, T object {};
on the other hand is value initialisation (of the class instance, not value initialisation of members!). Since it is a class type with a default constructor, the default constructor is called. The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int
(with non-static storage) leaves b.b
with an indeterminate value.
And for pod-types, the default initialization is zero-initialization.
No. This is wrong.
P.S. A word about your experiment and your conclusion: Seeing that output is zero does not necessarily mean that the variable was zero initialised. Zero is perfectly possible number for a garbage value.
for that I ran the program maybe 5~6 times before posting and about 10 times now, a is always zero. b changes around a little.
The fact that the value was same multiple times does not necessarily mean that it was initialised either.
I also tried with set(CMAKE_CXX_STANDARD 14). The result was the same.
The fact that result is the same with multiple compiler options doesn't mean that the variable is initialised. (Although in some cases, changing standard version can change whether it is initialised).
How could I somehow shake my RAM a little so that if there was zero there, it should now be something else
There is no guaranteed way in C++ to make uninitialised value value to appear nonzero.
Only way to know that a variable is initialised is to compare program to the rules of the language and verify that the rules say that it is initialised. In this case a.a
is indeed initialised.
"The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int leaves it with an indeterminate value." --> eh! "for pod-types, the default initialization is zero-initialization." or am I wrong?
– Joey Mallone
Jan 24 at 15:49
2
@JoeyMallone Default initialization of POD types is no initialization.
– NathanOliver
Jan 24 at 15:53
@NathanOliver, Then I am even more confused. Then how comea
is initialized. I was thinkinga
is default initialized and default initialization for a member POD is, zero-initialization. Isa
then just luckily always coming up zero, no matter how many times I run this program.
– Joey Mallone
Jan 24 at 16:00
@JoeyMalloneThen how come a is initialized.
Because it is value initialised.I was thinking a is default initialized
It is not.
– eerorika
Jan 24 at 16:00
3
@JoeyMallone Don't worry about it. You could make a book out of initialization in C++. If you get a chance CppCon on youtube has a few videos on initialization with the most disappointing (as in pointing out how bad it is) being youtube.com/watch?v=7DTlWPgX6zs
– NathanOliver
Jan 24 at 16:08
|
show 4 more comments
Meh, I tried running the snippet you provided as test.cpp
, through gcc & clang and multiple optimization levels:
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 ]
So that is where it gets interesting, it clearly shows clang O0 build is reading random numbers, presumably stack space.
I quickly turned up my IDA to see what's happening:
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;
}
Now, what does bar::bar(bar *this)
does?
void __fastcall bar::bar(bar *this)
{
;
}
Hmm, nothing. We had to resort to using assembly:
.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
So yeah, it's just, nothing, what the constructor basically does is this = this
. But we know that it is actually loading random uninitialized stack addresses and print it.
What if we explicitly provide values for the two structs?
#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 ]
Similar fate with g++ as well:
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 ]
So this means it's effectively a direct initialization bar b(0)
, not aggregate initialization.
This is probably because if you do not provide an explicit constructor implementation this could potentially be an external symbol, for example:
bar::bar() {
this.b = 1337; // whoa
}
The compiler isn't smart enough to deduce this as a no-op/an inline call in a non-optimized stage.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54350114%2fc-zero-initialization-why-is-b-in-this-program-uninitialized-but-a-is-i%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
The issue here is pretty subtle. You would think that
bar::bar() = default;
would give you a compiler generated default constructor, and it does, but it is now considered user provided. [dcl.fct.def.default]/5 states:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
emphasis mine
So we can see that since you did not default bar()
when you first declared it, it is now considered user provided. Because of that [dcl.init]/8.2
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
no longer applies and we are not value initializing b
but instead default initializing it per [dcl.init]/8.1
if T is a (possibly cv-qualified) class type ([class]) with either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
51
I mean(*_*)
.... If to even use the basic constructs of the language, I need to read the fine print of the language draft, then Hallelujah! But it probably seems to be what you say.
– Joey Mallone
Jan 24 at 15:46
11
@balki Yes, doingbar::bar() = default
out of line is the same as doingbar::bar(){}
inline.
– NathanOliver
Jan 24 at 15:47
14
@JoeyMallone Yeah, C++ can be pretty complicated. I'm not sure what the reason for this is.
– NathanOliver
Jan 24 at 15:49
3
If there is a previous declaration, then a subsequent definition with the default keyword will NOT zero initialize the members. Right? This is correct. It is what is happening here.
– NathanOliver
Jan 24 at 16:16
6
The reason is right there in your quote: the point of an out-of-line default is to "provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base", in other words, enable you to switch to a user-written body later if necessary without breaking ABI. Note that the out-of-line definition is not implicitly inline and so can only appear in one TU by default; another TU seeing the class definition alone has no way to know if it's explicitly defined as defaulted.
– T.C.
Jan 24 at 20:33
|
show 14 more comments
The issue here is pretty subtle. You would think that
bar::bar() = default;
would give you a compiler generated default constructor, and it does, but it is now considered user provided. [dcl.fct.def.default]/5 states:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
emphasis mine
So we can see that since you did not default bar()
when you first declared it, it is now considered user provided. Because of that [dcl.init]/8.2
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
no longer applies and we are not value initializing b
but instead default initializing it per [dcl.init]/8.1
if T is a (possibly cv-qualified) class type ([class]) with either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
51
I mean(*_*)
.... If to even use the basic constructs of the language, I need to read the fine print of the language draft, then Hallelujah! But it probably seems to be what you say.
– Joey Mallone
Jan 24 at 15:46
11
@balki Yes, doingbar::bar() = default
out of line is the same as doingbar::bar(){}
inline.
– NathanOliver
Jan 24 at 15:47
14
@JoeyMallone Yeah, C++ can be pretty complicated. I'm not sure what the reason for this is.
– NathanOliver
Jan 24 at 15:49
3
If there is a previous declaration, then a subsequent definition with the default keyword will NOT zero initialize the members. Right? This is correct. It is what is happening here.
– NathanOliver
Jan 24 at 16:16
6
The reason is right there in your quote: the point of an out-of-line default is to "provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base", in other words, enable you to switch to a user-written body later if necessary without breaking ABI. Note that the out-of-line definition is not implicitly inline and so can only appear in one TU by default; another TU seeing the class definition alone has no way to know if it's explicitly defined as defaulted.
– T.C.
Jan 24 at 20:33
|
show 14 more comments
The issue here is pretty subtle. You would think that
bar::bar() = default;
would give you a compiler generated default constructor, and it does, but it is now considered user provided. [dcl.fct.def.default]/5 states:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
emphasis mine
So we can see that since you did not default bar()
when you first declared it, it is now considered user provided. Because of that [dcl.init]/8.2
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
no longer applies and we are not value initializing b
but instead default initializing it per [dcl.init]/8.1
if T is a (possibly cv-qualified) class type ([class]) with either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
The issue here is pretty subtle. You would think that
bar::bar() = default;
would give you a compiler generated default constructor, and it does, but it is now considered user provided. [dcl.fct.def.default]/5 states:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
emphasis mine
So we can see that since you did not default bar()
when you first declared it, it is now considered user provided. Because of that [dcl.init]/8.2
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
no longer applies and we are not value initializing b
but instead default initializing it per [dcl.init]/8.1
if T is a (possibly cv-qualified) class type ([class]) with either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
edited Jan 24 at 15:54
answered Jan 24 at 15:41
NathanOliverNathanOliver
94.6k16131202
94.6k16131202
51
I mean(*_*)
.... If to even use the basic constructs of the language, I need to read the fine print of the language draft, then Hallelujah! But it probably seems to be what you say.
– Joey Mallone
Jan 24 at 15:46
11
@balki Yes, doingbar::bar() = default
out of line is the same as doingbar::bar(){}
inline.
– NathanOliver
Jan 24 at 15:47
14
@JoeyMallone Yeah, C++ can be pretty complicated. I'm not sure what the reason for this is.
– NathanOliver
Jan 24 at 15:49
3
If there is a previous declaration, then a subsequent definition with the default keyword will NOT zero initialize the members. Right? This is correct. It is what is happening here.
– NathanOliver
Jan 24 at 16:16
6
The reason is right there in your quote: the point of an out-of-line default is to "provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base", in other words, enable you to switch to a user-written body later if necessary without breaking ABI. Note that the out-of-line definition is not implicitly inline and so can only appear in one TU by default; another TU seeing the class definition alone has no way to know if it's explicitly defined as defaulted.
– T.C.
Jan 24 at 20:33
|
show 14 more comments
51
I mean(*_*)
.... If to even use the basic constructs of the language, I need to read the fine print of the language draft, then Hallelujah! But it probably seems to be what you say.
– Joey Mallone
Jan 24 at 15:46
11
@balki Yes, doingbar::bar() = default
out of line is the same as doingbar::bar(){}
inline.
– NathanOliver
Jan 24 at 15:47
14
@JoeyMallone Yeah, C++ can be pretty complicated. I'm not sure what the reason for this is.
– NathanOliver
Jan 24 at 15:49
3
If there is a previous declaration, then a subsequent definition with the default keyword will NOT zero initialize the members. Right? This is correct. It is what is happening here.
– NathanOliver
Jan 24 at 16:16
6
The reason is right there in your quote: the point of an out-of-line default is to "provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base", in other words, enable you to switch to a user-written body later if necessary without breaking ABI. Note that the out-of-line definition is not implicitly inline and so can only appear in one TU by default; another TU seeing the class definition alone has no way to know if it's explicitly defined as defaulted.
– T.C.
Jan 24 at 20:33
51
51
I mean
(*_*)
.... If to even use the basic constructs of the language, I need to read the fine print of the language draft, then Hallelujah! But it probably seems to be what you say.– Joey Mallone
Jan 24 at 15:46
I mean
(*_*)
.... If to even use the basic constructs of the language, I need to read the fine print of the language draft, then Hallelujah! But it probably seems to be what you say.– Joey Mallone
Jan 24 at 15:46
11
11
@balki Yes, doing
bar::bar() = default
out of line is the same as doing bar::bar(){}
inline.– NathanOliver
Jan 24 at 15:47
@balki Yes, doing
bar::bar() = default
out of line is the same as doing bar::bar(){}
inline.– NathanOliver
Jan 24 at 15:47
14
14
@JoeyMallone Yeah, C++ can be pretty complicated. I'm not sure what the reason for this is.
– NathanOliver
Jan 24 at 15:49
@JoeyMallone Yeah, C++ can be pretty complicated. I'm not sure what the reason for this is.
– NathanOliver
Jan 24 at 15:49
3
3
If there is a previous declaration, then a subsequent definition with the default keyword will NOT zero initialize the members. Right? This is correct. It is what is happening here.
– NathanOliver
Jan 24 at 16:16
If there is a previous declaration, then a subsequent definition with the default keyword will NOT zero initialize the members. Right? This is correct. It is what is happening here.
– NathanOliver
Jan 24 at 16:16
6
6
The reason is right there in your quote: the point of an out-of-line default is to "provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base", in other words, enable you to switch to a user-written body later if necessary without breaking ABI. Note that the out-of-line definition is not implicitly inline and so can only appear in one TU by default; another TU seeing the class definition alone has no way to know if it's explicitly defined as defaulted.
– T.C.
Jan 24 at 20:33
The reason is right there in your quote: the point of an out-of-line default is to "provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base", in other words, enable you to switch to a user-written body later if necessary without breaking ABI. Note that the out-of-line definition is not implicitly inline and so can only appear in one TU by default; another TU seeing the class definition alone has no way to know if it's explicitly defined as defaulted.
– T.C.
Jan 24 at 20:33
|
show 14 more comments
The difference in behaviour comes from the fact that, according to [dcl.fct.def.default]/5
, bar::bar
is user-provided where foo::foo
is not1. As a consequence, foo::foo
will value-initialize its members (meaning: zero-initialize foo::a
) but bar::bar
will stay uninitialized2.
1)[dcl.fct.def.default]/5
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
2)
From [dcl.init#6]:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
...
From [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
From Vittorio Romeo's answer
add a comment |
The difference in behaviour comes from the fact that, according to [dcl.fct.def.default]/5
, bar::bar
is user-provided where foo::foo
is not1. As a consequence, foo::foo
will value-initialize its members (meaning: zero-initialize foo::a
) but bar::bar
will stay uninitialized2.
1)[dcl.fct.def.default]/5
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
2)
From [dcl.init#6]:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
...
From [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
From Vittorio Romeo's answer
add a comment |
The difference in behaviour comes from the fact that, according to [dcl.fct.def.default]/5
, bar::bar
is user-provided where foo::foo
is not1. As a consequence, foo::foo
will value-initialize its members (meaning: zero-initialize foo::a
) but bar::bar
will stay uninitialized2.
1)[dcl.fct.def.default]/5
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
2)
From [dcl.init#6]:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
...
From [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
From Vittorio Romeo's answer
The difference in behaviour comes from the fact that, according to [dcl.fct.def.default]/5
, bar::bar
is user-provided where foo::foo
is not1. As a consequence, foo::foo
will value-initialize its members (meaning: zero-initialize foo::a
) but bar::bar
will stay uninitialized2.
1)[dcl.fct.def.default]/5
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
2)
From [dcl.init#6]:
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
...
From [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
From Vittorio Romeo's answer
edited Jan 24 at 15:50
answered Jan 24 at 15:40
YSCYSC
24.8k557112
24.8k557112
add a comment |
add a comment |
From cppreference:
Aggregate initialization initializes aggregates. It is a form of list-initialization.
An aggregate is one of the following types:
[snip]
class type [snip], that has
[snip] (there are variations for different standard versions)
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed)
[snip] (there are more rules, which apply to both classes)
Given this definition, foo
is an aggregate, while bar
is not (it has user-provided, non-defaulted constructor).
Therefore for foo
, T object {arg1, arg2, ...};
is syntax for aggregate initialisation.
The effects of aggregate initialization are:
[snip] (some details irrelevant to this case)
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized.
Therefore a.a
is value initialised, which for int
means zero initialisation.
For bar
, T object {};
on the other hand is value initialisation (of the class instance, not value initialisation of members!). Since it is a class type with a default constructor, the default constructor is called. The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int
(with non-static storage) leaves b.b
with an indeterminate value.
And for pod-types, the default initialization is zero-initialization.
No. This is wrong.
P.S. A word about your experiment and your conclusion: Seeing that output is zero does not necessarily mean that the variable was zero initialised. Zero is perfectly possible number for a garbage value.
for that I ran the program maybe 5~6 times before posting and about 10 times now, a is always zero. b changes around a little.
The fact that the value was same multiple times does not necessarily mean that it was initialised either.
I also tried with set(CMAKE_CXX_STANDARD 14). The result was the same.
The fact that result is the same with multiple compiler options doesn't mean that the variable is initialised. (Although in some cases, changing standard version can change whether it is initialised).
How could I somehow shake my RAM a little so that if there was zero there, it should now be something else
There is no guaranteed way in C++ to make uninitialised value value to appear nonzero.
Only way to know that a variable is initialised is to compare program to the rules of the language and verify that the rules say that it is initialised. In this case a.a
is indeed initialised.
"The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int leaves it with an indeterminate value." --> eh! "for pod-types, the default initialization is zero-initialization." or am I wrong?
– Joey Mallone
Jan 24 at 15:49
2
@JoeyMallone Default initialization of POD types is no initialization.
– NathanOliver
Jan 24 at 15:53
@NathanOliver, Then I am even more confused. Then how comea
is initialized. I was thinkinga
is default initialized and default initialization for a member POD is, zero-initialization. Isa
then just luckily always coming up zero, no matter how many times I run this program.
– Joey Mallone
Jan 24 at 16:00
@JoeyMalloneThen how come a is initialized.
Because it is value initialised.I was thinking a is default initialized
It is not.
– eerorika
Jan 24 at 16:00
3
@JoeyMallone Don't worry about it. You could make a book out of initialization in C++. If you get a chance CppCon on youtube has a few videos on initialization with the most disappointing (as in pointing out how bad it is) being youtube.com/watch?v=7DTlWPgX6zs
– NathanOliver
Jan 24 at 16:08
|
show 4 more comments
From cppreference:
Aggregate initialization initializes aggregates. It is a form of list-initialization.
An aggregate is one of the following types:
[snip]
class type [snip], that has
[snip] (there are variations for different standard versions)
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed)
[snip] (there are more rules, which apply to both classes)
Given this definition, foo
is an aggregate, while bar
is not (it has user-provided, non-defaulted constructor).
Therefore for foo
, T object {arg1, arg2, ...};
is syntax for aggregate initialisation.
The effects of aggregate initialization are:
[snip] (some details irrelevant to this case)
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized.
Therefore a.a
is value initialised, which for int
means zero initialisation.
For bar
, T object {};
on the other hand is value initialisation (of the class instance, not value initialisation of members!). Since it is a class type with a default constructor, the default constructor is called. The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int
(with non-static storage) leaves b.b
with an indeterminate value.
And for pod-types, the default initialization is zero-initialization.
No. This is wrong.
P.S. A word about your experiment and your conclusion: Seeing that output is zero does not necessarily mean that the variable was zero initialised. Zero is perfectly possible number for a garbage value.
for that I ran the program maybe 5~6 times before posting and about 10 times now, a is always zero. b changes around a little.
The fact that the value was same multiple times does not necessarily mean that it was initialised either.
I also tried with set(CMAKE_CXX_STANDARD 14). The result was the same.
The fact that result is the same with multiple compiler options doesn't mean that the variable is initialised. (Although in some cases, changing standard version can change whether it is initialised).
How could I somehow shake my RAM a little so that if there was zero there, it should now be something else
There is no guaranteed way in C++ to make uninitialised value value to appear nonzero.
Only way to know that a variable is initialised is to compare program to the rules of the language and verify that the rules say that it is initialised. In this case a.a
is indeed initialised.
"The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int leaves it with an indeterminate value." --> eh! "for pod-types, the default initialization is zero-initialization." or am I wrong?
– Joey Mallone
Jan 24 at 15:49
2
@JoeyMallone Default initialization of POD types is no initialization.
– NathanOliver
Jan 24 at 15:53
@NathanOliver, Then I am even more confused. Then how comea
is initialized. I was thinkinga
is default initialized and default initialization for a member POD is, zero-initialization. Isa
then just luckily always coming up zero, no matter how many times I run this program.
– Joey Mallone
Jan 24 at 16:00
@JoeyMalloneThen how come a is initialized.
Because it is value initialised.I was thinking a is default initialized
It is not.
– eerorika
Jan 24 at 16:00
3
@JoeyMallone Don't worry about it. You could make a book out of initialization in C++. If you get a chance CppCon on youtube has a few videos on initialization with the most disappointing (as in pointing out how bad it is) being youtube.com/watch?v=7DTlWPgX6zs
– NathanOliver
Jan 24 at 16:08
|
show 4 more comments
From cppreference:
Aggregate initialization initializes aggregates. It is a form of list-initialization.
An aggregate is one of the following types:
[snip]
class type [snip], that has
[snip] (there are variations for different standard versions)
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed)
[snip] (there are more rules, which apply to both classes)
Given this definition, foo
is an aggregate, while bar
is not (it has user-provided, non-defaulted constructor).
Therefore for foo
, T object {arg1, arg2, ...};
is syntax for aggregate initialisation.
The effects of aggregate initialization are:
[snip] (some details irrelevant to this case)
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized.
Therefore a.a
is value initialised, which for int
means zero initialisation.
For bar
, T object {};
on the other hand is value initialisation (of the class instance, not value initialisation of members!). Since it is a class type with a default constructor, the default constructor is called. The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int
(with non-static storage) leaves b.b
with an indeterminate value.
And for pod-types, the default initialization is zero-initialization.
No. This is wrong.
P.S. A word about your experiment and your conclusion: Seeing that output is zero does not necessarily mean that the variable was zero initialised. Zero is perfectly possible number for a garbage value.
for that I ran the program maybe 5~6 times before posting and about 10 times now, a is always zero. b changes around a little.
The fact that the value was same multiple times does not necessarily mean that it was initialised either.
I also tried with set(CMAKE_CXX_STANDARD 14). The result was the same.
The fact that result is the same with multiple compiler options doesn't mean that the variable is initialised. (Although in some cases, changing standard version can change whether it is initialised).
How could I somehow shake my RAM a little so that if there was zero there, it should now be something else
There is no guaranteed way in C++ to make uninitialised value value to appear nonzero.
Only way to know that a variable is initialised is to compare program to the rules of the language and verify that the rules say that it is initialised. In this case a.a
is indeed initialised.
From cppreference:
Aggregate initialization initializes aggregates. It is a form of list-initialization.
An aggregate is one of the following types:
[snip]
class type [snip], that has
[snip] (there are variations for different standard versions)
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed)
[snip] (there are more rules, which apply to both classes)
Given this definition, foo
is an aggregate, while bar
is not (it has user-provided, non-defaulted constructor).
Therefore for foo
, T object {arg1, arg2, ...};
is syntax for aggregate initialisation.
The effects of aggregate initialization are:
[snip] (some details irrelevant to this case)
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized.
Therefore a.a
is value initialised, which for int
means zero initialisation.
For bar
, T object {};
on the other hand is value initialisation (of the class instance, not value initialisation of members!). Since it is a class type with a default constructor, the default constructor is called. The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int
(with non-static storage) leaves b.b
with an indeterminate value.
And for pod-types, the default initialization is zero-initialization.
No. This is wrong.
P.S. A word about your experiment and your conclusion: Seeing that output is zero does not necessarily mean that the variable was zero initialised. Zero is perfectly possible number for a garbage value.
for that I ran the program maybe 5~6 times before posting and about 10 times now, a is always zero. b changes around a little.
The fact that the value was same multiple times does not necessarily mean that it was initialised either.
I also tried with set(CMAKE_CXX_STANDARD 14). The result was the same.
The fact that result is the same with multiple compiler options doesn't mean that the variable is initialised. (Although in some cases, changing standard version can change whether it is initialised).
How could I somehow shake my RAM a little so that if there was zero there, it should now be something else
There is no guaranteed way in C++ to make uninitialised value value to appear nonzero.
Only way to know that a variable is initialised is to compare program to the rules of the language and verify that the rules say that it is initialised. In this case a.a
is indeed initialised.
edited Jan 24 at 16:15
answered Jan 24 at 15:48
eerorikaeerorika
84.8k663132
84.8k663132
"The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int leaves it with an indeterminate value." --> eh! "for pod-types, the default initialization is zero-initialization." or am I wrong?
– Joey Mallone
Jan 24 at 15:49
2
@JoeyMallone Default initialization of POD types is no initialization.
– NathanOliver
Jan 24 at 15:53
@NathanOliver, Then I am even more confused. Then how comea
is initialized. I was thinkinga
is default initialized and default initialization for a member POD is, zero-initialization. Isa
then just luckily always coming up zero, no matter how many times I run this program.
– Joey Mallone
Jan 24 at 16:00
@JoeyMalloneThen how come a is initialized.
Because it is value initialised.I was thinking a is default initialized
It is not.
– eerorika
Jan 24 at 16:00
3
@JoeyMallone Don't worry about it. You could make a book out of initialization in C++. If you get a chance CppCon on youtube has a few videos on initialization with the most disappointing (as in pointing out how bad it is) being youtube.com/watch?v=7DTlWPgX6zs
– NathanOliver
Jan 24 at 16:08
|
show 4 more comments
"The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int leaves it with an indeterminate value." --> eh! "for pod-types, the default initialization is zero-initialization." or am I wrong?
– Joey Mallone
Jan 24 at 15:49
2
@JoeyMallone Default initialization of POD types is no initialization.
– NathanOliver
Jan 24 at 15:53
@NathanOliver, Then I am even more confused. Then how comea
is initialized. I was thinkinga
is default initialized and default initialization for a member POD is, zero-initialization. Isa
then just luckily always coming up zero, no matter how many times I run this program.
– Joey Mallone
Jan 24 at 16:00
@JoeyMalloneThen how come a is initialized.
Because it is value initialised.I was thinking a is default initialized
It is not.
– eerorika
Jan 24 at 16:00
3
@JoeyMallone Don't worry about it. You could make a book out of initialization in C++. If you get a chance CppCon on youtube has a few videos on initialization with the most disappointing (as in pointing out how bad it is) being youtube.com/watch?v=7DTlWPgX6zs
– NathanOliver
Jan 24 at 16:08
"The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int leaves it with an indeterminate value." --> eh! "for pod-types, the default initialization is zero-initialization." or am I wrong?
– Joey Mallone
Jan 24 at 15:49
"The default constructor that you defined default initialises the members (by virtue of not having member initialisers), which in case of int leaves it with an indeterminate value." --> eh! "for pod-types, the default initialization is zero-initialization." or am I wrong?
– Joey Mallone
Jan 24 at 15:49
2
2
@JoeyMallone Default initialization of POD types is no initialization.
– NathanOliver
Jan 24 at 15:53
@JoeyMallone Default initialization of POD types is no initialization.
– NathanOliver
Jan 24 at 15:53
@NathanOliver, Then I am even more confused. Then how come
a
is initialized. I was thinking a
is default initialized and default initialization for a member POD is, zero-initialization. Is a
then just luckily always coming up zero, no matter how many times I run this program.– Joey Mallone
Jan 24 at 16:00
@NathanOliver, Then I am even more confused. Then how come
a
is initialized. I was thinking a
is default initialized and default initialization for a member POD is, zero-initialization. Is a
then just luckily always coming up zero, no matter how many times I run this program.– Joey Mallone
Jan 24 at 16:00
@JoeyMallone
Then how come a is initialized.
Because it is value initialised. I was thinking a is default initialized
It is not.– eerorika
Jan 24 at 16:00
@JoeyMallone
Then how come a is initialized.
Because it is value initialised. I was thinking a is default initialized
It is not.– eerorika
Jan 24 at 16:00
3
3
@JoeyMallone Don't worry about it. You could make a book out of initialization in C++. If you get a chance CppCon on youtube has a few videos on initialization with the most disappointing (as in pointing out how bad it is) being youtube.com/watch?v=7DTlWPgX6zs
– NathanOliver
Jan 24 at 16:08
@JoeyMallone Don't worry about it. You could make a book out of initialization in C++. If you get a chance CppCon on youtube has a few videos on initialization with the most disappointing (as in pointing out how bad it is) being youtube.com/watch?v=7DTlWPgX6zs
– NathanOliver
Jan 24 at 16:08
|
show 4 more comments
Meh, I tried running the snippet you provided as test.cpp
, through gcc & clang and multiple optimization levels:
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 ]
So that is where it gets interesting, it clearly shows clang O0 build is reading random numbers, presumably stack space.
I quickly turned up my IDA to see what's happening:
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;
}
Now, what does bar::bar(bar *this)
does?
void __fastcall bar::bar(bar *this)
{
;
}
Hmm, nothing. We had to resort to using assembly:
.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
So yeah, it's just, nothing, what the constructor basically does is this = this
. But we know that it is actually loading random uninitialized stack addresses and print it.
What if we explicitly provide values for the two structs?
#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 ]
Similar fate with g++ as well:
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 ]
So this means it's effectively a direct initialization bar b(0)
, not aggregate initialization.
This is probably because if you do not provide an explicit constructor implementation this could potentially be an external symbol, for example:
bar::bar() {
this.b = 1337; // whoa
}
The compiler isn't smart enough to deduce this as a no-op/an inline call in a non-optimized stage.
add a comment |
Meh, I tried running the snippet you provided as test.cpp
, through gcc & clang and multiple optimization levels:
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 ]
So that is where it gets interesting, it clearly shows clang O0 build is reading random numbers, presumably stack space.
I quickly turned up my IDA to see what's happening:
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;
}
Now, what does bar::bar(bar *this)
does?
void __fastcall bar::bar(bar *this)
{
;
}
Hmm, nothing. We had to resort to using assembly:
.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
So yeah, it's just, nothing, what the constructor basically does is this = this
. But we know that it is actually loading random uninitialized stack addresses and print it.
What if we explicitly provide values for the two structs?
#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 ]
Similar fate with g++ as well:
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 ]
So this means it's effectively a direct initialization bar b(0)
, not aggregate initialization.
This is probably because if you do not provide an explicit constructor implementation this could potentially be an external symbol, for example:
bar::bar() {
this.b = 1337; // whoa
}
The compiler isn't smart enough to deduce this as a no-op/an inline call in a non-optimized stage.
add a comment |
Meh, I tried running the snippet you provided as test.cpp
, through gcc & clang and multiple optimization levels:
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 ]
So that is where it gets interesting, it clearly shows clang O0 build is reading random numbers, presumably stack space.
I quickly turned up my IDA to see what's happening:
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;
}
Now, what does bar::bar(bar *this)
does?
void __fastcall bar::bar(bar *this)
{
;
}
Hmm, nothing. We had to resort to using assembly:
.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
So yeah, it's just, nothing, what the constructor basically does is this = this
. But we know that it is actually loading random uninitialized stack addresses and print it.
What if we explicitly provide values for the two structs?
#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 ]
Similar fate with g++ as well:
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 ]
So this means it's effectively a direct initialization bar b(0)
, not aggregate initialization.
This is probably because if you do not provide an explicit constructor implementation this could potentially be an external symbol, for example:
bar::bar() {
this.b = 1337; // whoa
}
The compiler isn't smart enough to deduce this as a no-op/an inline call in a non-optimized stage.
Meh, I tried running the snippet you provided as test.cpp
, through gcc & clang and multiple optimization levels:
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 ]
So that is where it gets interesting, it clearly shows clang O0 build is reading random numbers, presumably stack space.
I quickly turned up my IDA to see what's happening:
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;
}
Now, what does bar::bar(bar *this)
does?
void __fastcall bar::bar(bar *this)
{
;
}
Hmm, nothing. We had to resort to using assembly:
.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
So yeah, it's just, nothing, what the constructor basically does is this = this
. But we know that it is actually loading random uninitialized stack addresses and print it.
What if we explicitly provide values for the two structs?
#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 ]
Similar fate with g++ as well:
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 ]
So this means it's effectively a direct initialization bar b(0)
, not aggregate initialization.
This is probably because if you do not provide an explicit constructor implementation this could potentially be an external symbol, for example:
bar::bar() {
this.b = 1337; // whoa
}
The compiler isn't smart enough to deduce this as a no-op/an inline call in a non-optimized stage.
edited Jan 27 at 5:55
answered Jan 27 at 5:45
Steve FanSteve Fan
99011120
99011120
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54350114%2fc-zero-initialization-why-is-b-in-this-program-uninitialized-but-a-is-i%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
3
Interestingly, I even get a warning for b: main.cpp:18:34: warning: 'b.bar::b' is used uninitialized in this function [-Wuninitialized] coliru.stacked-crooked.com/a/d1b08a4d6fb4ca7e
– tkausl
Jan 24 at 15:32
8
bar
's constructor is user provided whereasfoo
's constructor is the defaulted one.– Jarod42
Jan 24 at 15:33
2
@PeteBecker, I understand that. How could I somehow shake my RAM a little so that if there was zero there, it should now be something else. ;) p.s. I ran the program a dozen times. It is not a big program. You could run it and test it on your system.
a
is zero.b
is not. Seemsa
is initialized.– Joey Mallone
Jan 24 at 15:41
2
@JoeyMallone Regarding "how is it user-provided": There is no guarantee that the definition of
bar::bar()
is visible inmain()
- it might be defined in a separate compilation unit and do something very non-trivial while inmain()
only the declaration is visible. I think you'll agree that this behavior shouldn't change depending on whether you placebar::bar()
's definition in a separate compilation unit or not (even if the whole situation is unintuitive).– Max Langhof
Jan 24 at 15:47
2
@balki Or
int a = 0;
is you want to be really explicit.– NathanOliver
Jan 24 at 16:27