Standard-layout and tail padding
David Hollman recently tweeted the following example (which I've slightly reduced):
struct FooBeforeBase {
double d;
bool b[4];
};
struct FooBefore : FooBeforeBase {
float value;
};
static_assert(sizeof(FooBefore) > 16);
//----------------------------------------------------
struct FooAfterBase {
protected:
double d;
public:
bool b[4];
};
struct FooAfter : FooAfterBase {
float value;
};
static_assert(sizeof(FooAfter) == 16);
You can examine the layout in clang on godbolt and see that the reason the size changed is that in FooBefore
, the member value
is placed at offset 16 (maintaining a full alignment of 8 from FooBeforeBase
) whereas in FooAfter
, the member value
is placed at offset 12 (effectively using FooAfterBase
's tail-padding).
It is clear to me that FooBeforeBase
is standard-layout, but FooAfterBase
is not (because its non-static data members do not all have the same access control, [class.prop]/3). But what is it about FooBeforeBase
's being standard-layout that requires this respect of padding bytes?
Both gcc and clang reuse FooAfterBase
's padding, ending up with sizeof(FooAfter) == 16
. But MSVC does not, ending up with 24. Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
There is some confusion, so just to clear up:
FooBeforeBase
is standard-layout
FooBefore
is not (both it and a base class have non-static data members, similar toE
in this example)
FooAfterBase
is not (it has non-static data members of differing access)
FooAfter
is not (for both of the above reasons)
c++ g++ language-lawyer clang++ standard-layout
|
show 4 more comments
David Hollman recently tweeted the following example (which I've slightly reduced):
struct FooBeforeBase {
double d;
bool b[4];
};
struct FooBefore : FooBeforeBase {
float value;
};
static_assert(sizeof(FooBefore) > 16);
//----------------------------------------------------
struct FooAfterBase {
protected:
double d;
public:
bool b[4];
};
struct FooAfter : FooAfterBase {
float value;
};
static_assert(sizeof(FooAfter) == 16);
You can examine the layout in clang on godbolt and see that the reason the size changed is that in FooBefore
, the member value
is placed at offset 16 (maintaining a full alignment of 8 from FooBeforeBase
) whereas in FooAfter
, the member value
is placed at offset 12 (effectively using FooAfterBase
's tail-padding).
It is clear to me that FooBeforeBase
is standard-layout, but FooAfterBase
is not (because its non-static data members do not all have the same access control, [class.prop]/3). But what is it about FooBeforeBase
's being standard-layout that requires this respect of padding bytes?
Both gcc and clang reuse FooAfterBase
's padding, ending up with sizeof(FooAfter) == 16
. But MSVC does not, ending up with 24. Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
There is some confusion, so just to clear up:
FooBeforeBase
is standard-layout
FooBefore
is not (both it and a base class have non-static data members, similar toE
in this example)
FooAfterBase
is not (it has non-static data members of differing access)
FooAfter
is not (for both of the above reasons)
c++ g++ language-lawyer clang++ standard-layout
Who says that this behavior is "required" by anything in the standard? It could simply be a manifestation of how the compiler goes about implementing things.
– Nicol Bolas
Dec 18 '18 at 17:23
@NicolBolas It may very well not be required. MSVC does not do this (itsFooAfter
is also 24 bytes), but gcc and clang do - and it seems like that's a conscious choice on their parts.
– Barry
Dec 18 '18 at 17:32
"it seems like that's a conscious choice on their parts." What makes you say that?
– Nicol Bolas
Dec 18 '18 at 17:33
1
Related: stackoverflow.com/a/51334730/775806
– n.m.
Dec 18 '18 at 17:49
It is never required that there is no padding between class members. It may be required that there is padding. So the correct question is not which part of the standard requires gcc to reuse the end-padding, but what allows it to do so in the second case. Another question is whether something disallows such reuse in the first case.
– n.m.
Dec 18 '18 at 17:53
|
show 4 more comments
David Hollman recently tweeted the following example (which I've slightly reduced):
struct FooBeforeBase {
double d;
bool b[4];
};
struct FooBefore : FooBeforeBase {
float value;
};
static_assert(sizeof(FooBefore) > 16);
//----------------------------------------------------
struct FooAfterBase {
protected:
double d;
public:
bool b[4];
};
struct FooAfter : FooAfterBase {
float value;
};
static_assert(sizeof(FooAfter) == 16);
You can examine the layout in clang on godbolt and see that the reason the size changed is that in FooBefore
, the member value
is placed at offset 16 (maintaining a full alignment of 8 from FooBeforeBase
) whereas in FooAfter
, the member value
is placed at offset 12 (effectively using FooAfterBase
's tail-padding).
It is clear to me that FooBeforeBase
is standard-layout, but FooAfterBase
is not (because its non-static data members do not all have the same access control, [class.prop]/3). But what is it about FooBeforeBase
's being standard-layout that requires this respect of padding bytes?
Both gcc and clang reuse FooAfterBase
's padding, ending up with sizeof(FooAfter) == 16
. But MSVC does not, ending up with 24. Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
There is some confusion, so just to clear up:
FooBeforeBase
is standard-layout
FooBefore
is not (both it and a base class have non-static data members, similar toE
in this example)
FooAfterBase
is not (it has non-static data members of differing access)
FooAfter
is not (for both of the above reasons)
c++ g++ language-lawyer clang++ standard-layout
David Hollman recently tweeted the following example (which I've slightly reduced):
struct FooBeforeBase {
double d;
bool b[4];
};
struct FooBefore : FooBeforeBase {
float value;
};
static_assert(sizeof(FooBefore) > 16);
//----------------------------------------------------
struct FooAfterBase {
protected:
double d;
public:
bool b[4];
};
struct FooAfter : FooAfterBase {
float value;
};
static_assert(sizeof(FooAfter) == 16);
You can examine the layout in clang on godbolt and see that the reason the size changed is that in FooBefore
, the member value
is placed at offset 16 (maintaining a full alignment of 8 from FooBeforeBase
) whereas in FooAfter
, the member value
is placed at offset 12 (effectively using FooAfterBase
's tail-padding).
It is clear to me that FooBeforeBase
is standard-layout, but FooAfterBase
is not (because its non-static data members do not all have the same access control, [class.prop]/3). But what is it about FooBeforeBase
's being standard-layout that requires this respect of padding bytes?
Both gcc and clang reuse FooAfterBase
's padding, ending up with sizeof(FooAfter) == 16
. But MSVC does not, ending up with 24. Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
There is some confusion, so just to clear up:
FooBeforeBase
is standard-layout
FooBefore
is not (both it and a base class have non-static data members, similar toE
in this example)
FooAfterBase
is not (it has non-static data members of differing access)
FooAfter
is not (for both of the above reasons)
c++ g++ language-lawyer clang++ standard-layout
c++ g++ language-lawyer clang++ standard-layout
edited Dec 18 '18 at 17:34
Barry
asked Dec 18 '18 at 16:32
BarryBarry
179k19310572
179k19310572
Who says that this behavior is "required" by anything in the standard? It could simply be a manifestation of how the compiler goes about implementing things.
– Nicol Bolas
Dec 18 '18 at 17:23
@NicolBolas It may very well not be required. MSVC does not do this (itsFooAfter
is also 24 bytes), but gcc and clang do - and it seems like that's a conscious choice on their parts.
– Barry
Dec 18 '18 at 17:32
"it seems like that's a conscious choice on their parts." What makes you say that?
– Nicol Bolas
Dec 18 '18 at 17:33
1
Related: stackoverflow.com/a/51334730/775806
– n.m.
Dec 18 '18 at 17:49
It is never required that there is no padding between class members. It may be required that there is padding. So the correct question is not which part of the standard requires gcc to reuse the end-padding, but what allows it to do so in the second case. Another question is whether something disallows such reuse in the first case.
– n.m.
Dec 18 '18 at 17:53
|
show 4 more comments
Who says that this behavior is "required" by anything in the standard? It could simply be a manifestation of how the compiler goes about implementing things.
– Nicol Bolas
Dec 18 '18 at 17:23
@NicolBolas It may very well not be required. MSVC does not do this (itsFooAfter
is also 24 bytes), but gcc and clang do - and it seems like that's a conscious choice on their parts.
– Barry
Dec 18 '18 at 17:32
"it seems like that's a conscious choice on their parts." What makes you say that?
– Nicol Bolas
Dec 18 '18 at 17:33
1
Related: stackoverflow.com/a/51334730/775806
– n.m.
Dec 18 '18 at 17:49
It is never required that there is no padding between class members. It may be required that there is padding. So the correct question is not which part of the standard requires gcc to reuse the end-padding, but what allows it to do so in the second case. Another question is whether something disallows such reuse in the first case.
– n.m.
Dec 18 '18 at 17:53
Who says that this behavior is "required" by anything in the standard? It could simply be a manifestation of how the compiler goes about implementing things.
– Nicol Bolas
Dec 18 '18 at 17:23
Who says that this behavior is "required" by anything in the standard? It could simply be a manifestation of how the compiler goes about implementing things.
– Nicol Bolas
Dec 18 '18 at 17:23
@NicolBolas It may very well not be required. MSVC does not do this (its
FooAfter
is also 24 bytes), but gcc and clang do - and it seems like that's a conscious choice on their parts.– Barry
Dec 18 '18 at 17:32
@NicolBolas It may very well not be required. MSVC does not do this (its
FooAfter
is also 24 bytes), but gcc and clang do - and it seems like that's a conscious choice on their parts.– Barry
Dec 18 '18 at 17:32
"it seems like that's a conscious choice on their parts." What makes you say that?
– Nicol Bolas
Dec 18 '18 at 17:33
"it seems like that's a conscious choice on their parts." What makes you say that?
– Nicol Bolas
Dec 18 '18 at 17:33
1
1
Related: stackoverflow.com/a/51334730/775806
– n.m.
Dec 18 '18 at 17:49
Related: stackoverflow.com/a/51334730/775806
– n.m.
Dec 18 '18 at 17:49
It is never required that there is no padding between class members. It may be required that there is padding. So the correct question is not which part of the standard requires gcc to reuse the end-padding, but what allows it to do so in the second case. Another question is whether something disallows such reuse in the first case.
– n.m.
Dec 18 '18 at 17:53
It is never required that there is no padding between class members. It may be required that there is padding. So the correct question is not which part of the standard requires gcc to reuse the end-padding, but what allows it to do so in the second case. Another question is whether something disallows such reuse in the first case.
– n.m.
Dec 18 '18 at 17:53
|
show 4 more comments
5 Answers
5
active
oldest
votes
The answer to this question doesn't come from the standard but rather from the Itanium ABI (which is why gcc and clang have one behavior but msvc does something else). That ABI defines a layout, the relevant parts of which for the purposes of this question are:
For purposes internal to the specification, we also specify:
dsize(O): the data size of an object, which is the size of O without tail padding.
and
We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.
Where the placement of members other than virtual base classes is defined as:
Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless [... not relevant ...].
The term POD has disappeared from the C++ standard, but it means standard-layout and trivially copyable. In this question, FooBeforeBase
is a POD. The Itanium ABI ignores tail padding - hence dsize(FooBeforeBase)
is 16.
But FooAfterBase
is not a POD (it is trivially copyable, but it is not standard-layout). As a result, tail padding is not ignored, so dsize(FooAfterBase)
is just 12, and the float
can go right there.
This has interesting consequences, as pointed out by Quuxplusone in a related answer, implementors also typically assume that tail padding isn't reused, which wreaks havoc on this example:
#include <algorithm>
#include <stdio.h>
struct A {
int m_a;
};
struct B : A {
int m_b1;
char m_b2;
};
struct C : B {
short m_c;
};
int main() {
C c1 { 1, 2, 3, 4 };
B& b1 = c1;
B b2 { 5, 6, 7 };
printf("before operator=: %dn", int(c1.m_c)); // 4
b1 = b2;
printf("after operator=: %dn", int(c1.m_c)); // 4
printf("before std::copy: %dn", int(c1.m_c)); // 4
std::copy(&b2, &b2 + 1, &b1);
printf("after std::copy: %dn", int(c1.m_c)); // 64, or 0, or anything but 4
}
Here, =
does the right thing (it does not override B
's tail padding), but copy()
has a library optimization that reduces to memmove()
- which does not care about tail padding because it assumes it does not exist.
add a comment |
FooBefore derived;
FooBeforeBase src, &dst=derived;
....
memcpy(&dst, &src, sizeof(dst));
If the additional data member was placed in the hole, memcpy
would have overwritten it.
As is correctly pointed out in comments, the standard doesn't require that this memcpy
invocation should work. However the Itanium ABI is seemingly designed with this case in mind. Perhaps the ABI rules are specified this way in order to make mixed-language programming a bit more robust, or to preserve some kind of backwards compatibility.
Relevant ABI rules can be found here.
A related answer can be found here (this question might be a duplicate of that one).
1
TriviallyCopyable does not work if you try to copy into a base class subobject.
– Nicol Bolas
Dec 18 '18 at 17:18
The next question: why does the Itanium ABI designed this way? :)
– geza
Dec 18 '18 at 18:02
@geza I don't know but my guess is up there in the answer.
– n.m.
Dec 18 '18 at 18:09
add a comment |
Here is a concrete case which demonstrates why the second case cannot reuse the padding:
union bob {
FooBeforeBase a;
FooBefore b;
};
bob.b.value = 3.14;
memset( &bob.a, 0, sizeof(bob.a) );
this cannot clear bob.b.value
.
union bob2 {
FooAfterBase a;
FooAfter b;
};
bob2.b.value = 3.14;
memset( &bob2.a, 0, sizeof(bob2.a) );
this is undefined behavior.
"this cannot clear bob.b.value." SinceFooBefore
is not standard layout, the common-initial-sequence rule doesn't apply. So you can't accessbob.a
after you've setbob.b
.
– Nicol Bolas
Dec 18 '18 at 17:04
4
@Holt: bothFooBeforeBase
andFooBefore
has non-static members, thereforeFooBefore
has no standard layout.
– geza
Dec 18 '18 at 17:15
2
@Holt A standard layout type has data members either in a base class or not in a base class, but not both. en.cppreference.com/w/cpp/named_req/StandardLayoutType
– n.m.
Dec 18 '18 at 17:19
1
@Holt: It's the "has no element of the set" part. That's what it says once you untangle all of the spec-language.
– Nicol Bolas
Dec 18 '18 at 17:19
1
CIS doesn't matter anyway. It allows reading through a different member, not writing.
– T.C.
Dec 20 '18 at 7:49
|
show 3 more comments
FooBefore
is not std-layout either; two classes are declaring none-static data members(FooBefore
and FooBeforeBase
). Thus the compiler is allowed to arbitrarily place some data members. Hence the differences on different tool-chains arise.
In a std-layout hierarchy, atmost one class(either the most derived class or at most one intermediate class) shall declare none-static data members.
add a comment |
Here's a similar case as n.m.'s answer.
First, let's have a function, which clears a FooBeforeBase
:
void clearBase(FooBeforeBase *f) {
memset(f, 0, sizeof(*f));
}
This is fine, as clearBase
gets a pointer to FooBeforeBase
, it thinks that as FooBeforeBase
has standard-layout, so memsetting it is safe.
Now, if you do this:
FooBefore b;
b.value = 42;
clearBase(&b);
You don't expect, that clearBase
will clear b.value
, as b.value
is not part of FooBeforeBase
. But, if FooBefore::value
was put into tail-padding of FooBeforeBase
, it would been cleared as well.
Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
No, tail-padding is not required. It is an optimization, which gcc and clang do.
But the standard doesn't allowclearBase
to work on a base class subobject. Well, if we're going to be technical,memset
isn't allowed on TriviallyCopyable types period, but even if that were amemcpy
from a zero-initializedFooBeforeBase
static
instance, it still wouldn't be allowed on base class subobjects.
– Nicol Bolas
Dec 18 '18 at 17:51
@NicolBolas: I nowhere stated that. As a user ofclearBase
, you may not know, what is inside. So, this behavior of the compiler guarantees that you don't shoot yourself in the foot. Please be a little bit more practical here, even if the question has the language-lawyer tag. We already talking about something, which is not covered by the standard (i.e., tail-padding optimization).
– geza
Dec 18 '18 at 17:55
1
"I nowhere stated that". You very much did, right after you said: "Now, if you do this:". That's code which callsclearBase
on a base class subobject, which is UB.
– Nicol Bolas
Dec 18 '18 at 17:56
1
"But the standard doesn't allow clearBase to work" Rather, it doesn't guarantee that it will work.
– n.m.
Dec 18 '18 at 17:57
@NicolBolas: okay then. This behavior is there too guarantee, that even if it is UB, it doesn't do any harm. UB doesn't automatically mean that something bad must happen.
– geza
Dec 18 '18 at 18:01
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%2f53837373%2fstandard-layout-and-tail-padding%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
The answer to this question doesn't come from the standard but rather from the Itanium ABI (which is why gcc and clang have one behavior but msvc does something else). That ABI defines a layout, the relevant parts of which for the purposes of this question are:
For purposes internal to the specification, we also specify:
dsize(O): the data size of an object, which is the size of O without tail padding.
and
We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.
Where the placement of members other than virtual base classes is defined as:
Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless [... not relevant ...].
The term POD has disappeared from the C++ standard, but it means standard-layout and trivially copyable. In this question, FooBeforeBase
is a POD. The Itanium ABI ignores tail padding - hence dsize(FooBeforeBase)
is 16.
But FooAfterBase
is not a POD (it is trivially copyable, but it is not standard-layout). As a result, tail padding is not ignored, so dsize(FooAfterBase)
is just 12, and the float
can go right there.
This has interesting consequences, as pointed out by Quuxplusone in a related answer, implementors also typically assume that tail padding isn't reused, which wreaks havoc on this example:
#include <algorithm>
#include <stdio.h>
struct A {
int m_a;
};
struct B : A {
int m_b1;
char m_b2;
};
struct C : B {
short m_c;
};
int main() {
C c1 { 1, 2, 3, 4 };
B& b1 = c1;
B b2 { 5, 6, 7 };
printf("before operator=: %dn", int(c1.m_c)); // 4
b1 = b2;
printf("after operator=: %dn", int(c1.m_c)); // 4
printf("before std::copy: %dn", int(c1.m_c)); // 4
std::copy(&b2, &b2 + 1, &b1);
printf("after std::copy: %dn", int(c1.m_c)); // 64, or 0, or anything but 4
}
Here, =
does the right thing (it does not override B
's tail padding), but copy()
has a library optimization that reduces to memmove()
- which does not care about tail padding because it assumes it does not exist.
add a comment |
The answer to this question doesn't come from the standard but rather from the Itanium ABI (which is why gcc and clang have one behavior but msvc does something else). That ABI defines a layout, the relevant parts of which for the purposes of this question are:
For purposes internal to the specification, we also specify:
dsize(O): the data size of an object, which is the size of O without tail padding.
and
We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.
Where the placement of members other than virtual base classes is defined as:
Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless [... not relevant ...].
The term POD has disappeared from the C++ standard, but it means standard-layout and trivially copyable. In this question, FooBeforeBase
is a POD. The Itanium ABI ignores tail padding - hence dsize(FooBeforeBase)
is 16.
But FooAfterBase
is not a POD (it is trivially copyable, but it is not standard-layout). As a result, tail padding is not ignored, so dsize(FooAfterBase)
is just 12, and the float
can go right there.
This has interesting consequences, as pointed out by Quuxplusone in a related answer, implementors also typically assume that tail padding isn't reused, which wreaks havoc on this example:
#include <algorithm>
#include <stdio.h>
struct A {
int m_a;
};
struct B : A {
int m_b1;
char m_b2;
};
struct C : B {
short m_c;
};
int main() {
C c1 { 1, 2, 3, 4 };
B& b1 = c1;
B b2 { 5, 6, 7 };
printf("before operator=: %dn", int(c1.m_c)); // 4
b1 = b2;
printf("after operator=: %dn", int(c1.m_c)); // 4
printf("before std::copy: %dn", int(c1.m_c)); // 4
std::copy(&b2, &b2 + 1, &b1);
printf("after std::copy: %dn", int(c1.m_c)); // 64, or 0, or anything but 4
}
Here, =
does the right thing (it does not override B
's tail padding), but copy()
has a library optimization that reduces to memmove()
- which does not care about tail padding because it assumes it does not exist.
add a comment |
The answer to this question doesn't come from the standard but rather from the Itanium ABI (which is why gcc and clang have one behavior but msvc does something else). That ABI defines a layout, the relevant parts of which for the purposes of this question are:
For purposes internal to the specification, we also specify:
dsize(O): the data size of an object, which is the size of O without tail padding.
and
We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.
Where the placement of members other than virtual base classes is defined as:
Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless [... not relevant ...].
The term POD has disappeared from the C++ standard, but it means standard-layout and trivially copyable. In this question, FooBeforeBase
is a POD. The Itanium ABI ignores tail padding - hence dsize(FooBeforeBase)
is 16.
But FooAfterBase
is not a POD (it is trivially copyable, but it is not standard-layout). As a result, tail padding is not ignored, so dsize(FooAfterBase)
is just 12, and the float
can go right there.
This has interesting consequences, as pointed out by Quuxplusone in a related answer, implementors also typically assume that tail padding isn't reused, which wreaks havoc on this example:
#include <algorithm>
#include <stdio.h>
struct A {
int m_a;
};
struct B : A {
int m_b1;
char m_b2;
};
struct C : B {
short m_c;
};
int main() {
C c1 { 1, 2, 3, 4 };
B& b1 = c1;
B b2 { 5, 6, 7 };
printf("before operator=: %dn", int(c1.m_c)); // 4
b1 = b2;
printf("after operator=: %dn", int(c1.m_c)); // 4
printf("before std::copy: %dn", int(c1.m_c)); // 4
std::copy(&b2, &b2 + 1, &b1);
printf("after std::copy: %dn", int(c1.m_c)); // 64, or 0, or anything but 4
}
Here, =
does the right thing (it does not override B
's tail padding), but copy()
has a library optimization that reduces to memmove()
- which does not care about tail padding because it assumes it does not exist.
The answer to this question doesn't come from the standard but rather from the Itanium ABI (which is why gcc and clang have one behavior but msvc does something else). That ABI defines a layout, the relevant parts of which for the purposes of this question are:
For purposes internal to the specification, we also specify:
dsize(O): the data size of an object, which is the size of O without tail padding.
and
We ignore tail padding for PODs because an early version of the standard did not allow us to use it for anything else and because it sometimes permits faster copying of the type.
Where the placement of members other than virtual base classes is defined as:
Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless [... not relevant ...].
The term POD has disappeared from the C++ standard, but it means standard-layout and trivially copyable. In this question, FooBeforeBase
is a POD. The Itanium ABI ignores tail padding - hence dsize(FooBeforeBase)
is 16.
But FooAfterBase
is not a POD (it is trivially copyable, but it is not standard-layout). As a result, tail padding is not ignored, so dsize(FooAfterBase)
is just 12, and the float
can go right there.
This has interesting consequences, as pointed out by Quuxplusone in a related answer, implementors also typically assume that tail padding isn't reused, which wreaks havoc on this example:
#include <algorithm>
#include <stdio.h>
struct A {
int m_a;
};
struct B : A {
int m_b1;
char m_b2;
};
struct C : B {
short m_c;
};
int main() {
C c1 { 1, 2, 3, 4 };
B& b1 = c1;
B b2 { 5, 6, 7 };
printf("before operator=: %dn", int(c1.m_c)); // 4
b1 = b2;
printf("after operator=: %dn", int(c1.m_c)); // 4
printf("before std::copy: %dn", int(c1.m_c)); // 4
std::copy(&b2, &b2 + 1, &b1);
printf("after std::copy: %dn", int(c1.m_c)); // 64, or 0, or anything but 4
}
Here, =
does the right thing (it does not override B
's tail padding), but copy()
has a library optimization that reduces to memmove()
- which does not care about tail padding because it assumes it does not exist.
answered Dec 18 '18 at 18:46
community wiki
Barry
add a comment |
add a comment |
FooBefore derived;
FooBeforeBase src, &dst=derived;
....
memcpy(&dst, &src, sizeof(dst));
If the additional data member was placed in the hole, memcpy
would have overwritten it.
As is correctly pointed out in comments, the standard doesn't require that this memcpy
invocation should work. However the Itanium ABI is seemingly designed with this case in mind. Perhaps the ABI rules are specified this way in order to make mixed-language programming a bit more robust, or to preserve some kind of backwards compatibility.
Relevant ABI rules can be found here.
A related answer can be found here (this question might be a duplicate of that one).
1
TriviallyCopyable does not work if you try to copy into a base class subobject.
– Nicol Bolas
Dec 18 '18 at 17:18
The next question: why does the Itanium ABI designed this way? :)
– geza
Dec 18 '18 at 18:02
@geza I don't know but my guess is up there in the answer.
– n.m.
Dec 18 '18 at 18:09
add a comment |
FooBefore derived;
FooBeforeBase src, &dst=derived;
....
memcpy(&dst, &src, sizeof(dst));
If the additional data member was placed in the hole, memcpy
would have overwritten it.
As is correctly pointed out in comments, the standard doesn't require that this memcpy
invocation should work. However the Itanium ABI is seemingly designed with this case in mind. Perhaps the ABI rules are specified this way in order to make mixed-language programming a bit more robust, or to preserve some kind of backwards compatibility.
Relevant ABI rules can be found here.
A related answer can be found here (this question might be a duplicate of that one).
1
TriviallyCopyable does not work if you try to copy into a base class subobject.
– Nicol Bolas
Dec 18 '18 at 17:18
The next question: why does the Itanium ABI designed this way? :)
– geza
Dec 18 '18 at 18:02
@geza I don't know but my guess is up there in the answer.
– n.m.
Dec 18 '18 at 18:09
add a comment |
FooBefore derived;
FooBeforeBase src, &dst=derived;
....
memcpy(&dst, &src, sizeof(dst));
If the additional data member was placed in the hole, memcpy
would have overwritten it.
As is correctly pointed out in comments, the standard doesn't require that this memcpy
invocation should work. However the Itanium ABI is seemingly designed with this case in mind. Perhaps the ABI rules are specified this way in order to make mixed-language programming a bit more robust, or to preserve some kind of backwards compatibility.
Relevant ABI rules can be found here.
A related answer can be found here (this question might be a duplicate of that one).
FooBefore derived;
FooBeforeBase src, &dst=derived;
....
memcpy(&dst, &src, sizeof(dst));
If the additional data member was placed in the hole, memcpy
would have overwritten it.
As is correctly pointed out in comments, the standard doesn't require that this memcpy
invocation should work. However the Itanium ABI is seemingly designed with this case in mind. Perhaps the ABI rules are specified this way in order to make mixed-language programming a bit more robust, or to preserve some kind of backwards compatibility.
Relevant ABI rules can be found here.
A related answer can be found here (this question might be a duplicate of that one).
edited Dec 18 '18 at 18:10
answered Dec 18 '18 at 17:14
n.m.n.m.
71.5k882167
71.5k882167
1
TriviallyCopyable does not work if you try to copy into a base class subobject.
– Nicol Bolas
Dec 18 '18 at 17:18
The next question: why does the Itanium ABI designed this way? :)
– geza
Dec 18 '18 at 18:02
@geza I don't know but my guess is up there in the answer.
– n.m.
Dec 18 '18 at 18:09
add a comment |
1
TriviallyCopyable does not work if you try to copy into a base class subobject.
– Nicol Bolas
Dec 18 '18 at 17:18
The next question: why does the Itanium ABI designed this way? :)
– geza
Dec 18 '18 at 18:02
@geza I don't know but my guess is up there in the answer.
– n.m.
Dec 18 '18 at 18:09
1
1
TriviallyCopyable does not work if you try to copy into a base class subobject.
– Nicol Bolas
Dec 18 '18 at 17:18
TriviallyCopyable does not work if you try to copy into a base class subobject.
– Nicol Bolas
Dec 18 '18 at 17:18
The next question: why does the Itanium ABI designed this way? :)
– geza
Dec 18 '18 at 18:02
The next question: why does the Itanium ABI designed this way? :)
– geza
Dec 18 '18 at 18:02
@geza I don't know but my guess is up there in the answer.
– n.m.
Dec 18 '18 at 18:09
@geza I don't know but my guess is up there in the answer.
– n.m.
Dec 18 '18 at 18:09
add a comment |
Here is a concrete case which demonstrates why the second case cannot reuse the padding:
union bob {
FooBeforeBase a;
FooBefore b;
};
bob.b.value = 3.14;
memset( &bob.a, 0, sizeof(bob.a) );
this cannot clear bob.b.value
.
union bob2 {
FooAfterBase a;
FooAfter b;
};
bob2.b.value = 3.14;
memset( &bob2.a, 0, sizeof(bob2.a) );
this is undefined behavior.
"this cannot clear bob.b.value." SinceFooBefore
is not standard layout, the common-initial-sequence rule doesn't apply. So you can't accessbob.a
after you've setbob.b
.
– Nicol Bolas
Dec 18 '18 at 17:04
4
@Holt: bothFooBeforeBase
andFooBefore
has non-static members, thereforeFooBefore
has no standard layout.
– geza
Dec 18 '18 at 17:15
2
@Holt A standard layout type has data members either in a base class or not in a base class, but not both. en.cppreference.com/w/cpp/named_req/StandardLayoutType
– n.m.
Dec 18 '18 at 17:19
1
@Holt: It's the "has no element of the set" part. That's what it says once you untangle all of the spec-language.
– Nicol Bolas
Dec 18 '18 at 17:19
1
CIS doesn't matter anyway. It allows reading through a different member, not writing.
– T.C.
Dec 20 '18 at 7:49
|
show 3 more comments
Here is a concrete case which demonstrates why the second case cannot reuse the padding:
union bob {
FooBeforeBase a;
FooBefore b;
};
bob.b.value = 3.14;
memset( &bob.a, 0, sizeof(bob.a) );
this cannot clear bob.b.value
.
union bob2 {
FooAfterBase a;
FooAfter b;
};
bob2.b.value = 3.14;
memset( &bob2.a, 0, sizeof(bob2.a) );
this is undefined behavior.
"this cannot clear bob.b.value." SinceFooBefore
is not standard layout, the common-initial-sequence rule doesn't apply. So you can't accessbob.a
after you've setbob.b
.
– Nicol Bolas
Dec 18 '18 at 17:04
4
@Holt: bothFooBeforeBase
andFooBefore
has non-static members, thereforeFooBefore
has no standard layout.
– geza
Dec 18 '18 at 17:15
2
@Holt A standard layout type has data members either in a base class or not in a base class, but not both. en.cppreference.com/w/cpp/named_req/StandardLayoutType
– n.m.
Dec 18 '18 at 17:19
1
@Holt: It's the "has no element of the set" part. That's what it says once you untangle all of the spec-language.
– Nicol Bolas
Dec 18 '18 at 17:19
1
CIS doesn't matter anyway. It allows reading through a different member, not writing.
– T.C.
Dec 20 '18 at 7:49
|
show 3 more comments
Here is a concrete case which demonstrates why the second case cannot reuse the padding:
union bob {
FooBeforeBase a;
FooBefore b;
};
bob.b.value = 3.14;
memset( &bob.a, 0, sizeof(bob.a) );
this cannot clear bob.b.value
.
union bob2 {
FooAfterBase a;
FooAfter b;
};
bob2.b.value = 3.14;
memset( &bob2.a, 0, sizeof(bob2.a) );
this is undefined behavior.
Here is a concrete case which demonstrates why the second case cannot reuse the padding:
union bob {
FooBeforeBase a;
FooBefore b;
};
bob.b.value = 3.14;
memset( &bob.a, 0, sizeof(bob.a) );
this cannot clear bob.b.value
.
union bob2 {
FooAfterBase a;
FooAfter b;
};
bob2.b.value = 3.14;
memset( &bob2.a, 0, sizeof(bob2.a) );
this is undefined behavior.
answered Dec 18 '18 at 16:59
Yakk - Adam NevraumontYakk - Adam Nevraumont
183k19191376
183k19191376
"this cannot clear bob.b.value." SinceFooBefore
is not standard layout, the common-initial-sequence rule doesn't apply. So you can't accessbob.a
after you've setbob.b
.
– Nicol Bolas
Dec 18 '18 at 17:04
4
@Holt: bothFooBeforeBase
andFooBefore
has non-static members, thereforeFooBefore
has no standard layout.
– geza
Dec 18 '18 at 17:15
2
@Holt A standard layout type has data members either in a base class or not in a base class, but not both. en.cppreference.com/w/cpp/named_req/StandardLayoutType
– n.m.
Dec 18 '18 at 17:19
1
@Holt: It's the "has no element of the set" part. That's what it says once you untangle all of the spec-language.
– Nicol Bolas
Dec 18 '18 at 17:19
1
CIS doesn't matter anyway. It allows reading through a different member, not writing.
– T.C.
Dec 20 '18 at 7:49
|
show 3 more comments
"this cannot clear bob.b.value." SinceFooBefore
is not standard layout, the common-initial-sequence rule doesn't apply. So you can't accessbob.a
after you've setbob.b
.
– Nicol Bolas
Dec 18 '18 at 17:04
4
@Holt: bothFooBeforeBase
andFooBefore
has non-static members, thereforeFooBefore
has no standard layout.
– geza
Dec 18 '18 at 17:15
2
@Holt A standard layout type has data members either in a base class or not in a base class, but not both. en.cppreference.com/w/cpp/named_req/StandardLayoutType
– n.m.
Dec 18 '18 at 17:19
1
@Holt: It's the "has no element of the set" part. That's what it says once you untangle all of the spec-language.
– Nicol Bolas
Dec 18 '18 at 17:19
1
CIS doesn't matter anyway. It allows reading through a different member, not writing.
– T.C.
Dec 20 '18 at 7:49
"this cannot clear bob.b.value." Since
FooBefore
is not standard layout, the common-initial-sequence rule doesn't apply. So you can't access bob.a
after you've set bob.b
.– Nicol Bolas
Dec 18 '18 at 17:04
"this cannot clear bob.b.value." Since
FooBefore
is not standard layout, the common-initial-sequence rule doesn't apply. So you can't access bob.a
after you've set bob.b
.– Nicol Bolas
Dec 18 '18 at 17:04
4
4
@Holt: both
FooBeforeBase
and FooBefore
has non-static members, therefore FooBefore
has no standard layout.– geza
Dec 18 '18 at 17:15
@Holt: both
FooBeforeBase
and FooBefore
has non-static members, therefore FooBefore
has no standard layout.– geza
Dec 18 '18 at 17:15
2
2
@Holt A standard layout type has data members either in a base class or not in a base class, but not both. en.cppreference.com/w/cpp/named_req/StandardLayoutType
– n.m.
Dec 18 '18 at 17:19
@Holt A standard layout type has data members either in a base class or not in a base class, but not both. en.cppreference.com/w/cpp/named_req/StandardLayoutType
– n.m.
Dec 18 '18 at 17:19
1
1
@Holt: It's the "has no element of the set" part. That's what it says once you untangle all of the spec-language.
– Nicol Bolas
Dec 18 '18 at 17:19
@Holt: It's the "has no element of the set" part. That's what it says once you untangle all of the spec-language.
– Nicol Bolas
Dec 18 '18 at 17:19
1
1
CIS doesn't matter anyway. It allows reading through a different member, not writing.
– T.C.
Dec 20 '18 at 7:49
CIS doesn't matter anyway. It allows reading through a different member, not writing.
– T.C.
Dec 20 '18 at 7:49
|
show 3 more comments
FooBefore
is not std-layout either; two classes are declaring none-static data members(FooBefore
and FooBeforeBase
). Thus the compiler is allowed to arbitrarily place some data members. Hence the differences on different tool-chains arise.
In a std-layout hierarchy, atmost one class(either the most derived class or at most one intermediate class) shall declare none-static data members.
add a comment |
FooBefore
is not std-layout either; two classes are declaring none-static data members(FooBefore
and FooBeforeBase
). Thus the compiler is allowed to arbitrarily place some data members. Hence the differences on different tool-chains arise.
In a std-layout hierarchy, atmost one class(either the most derived class or at most one intermediate class) shall declare none-static data members.
add a comment |
FooBefore
is not std-layout either; two classes are declaring none-static data members(FooBefore
and FooBeforeBase
). Thus the compiler is allowed to arbitrarily place some data members. Hence the differences on different tool-chains arise.
In a std-layout hierarchy, atmost one class(either the most derived class or at most one intermediate class) shall declare none-static data members.
FooBefore
is not std-layout either; two classes are declaring none-static data members(FooBefore
and FooBeforeBase
). Thus the compiler is allowed to arbitrarily place some data members. Hence the differences on different tool-chains arise.
In a std-layout hierarchy, atmost one class(either the most derived class or at most one intermediate class) shall declare none-static data members.
answered Dec 18 '18 at 18:41
Red.WaveRed.Wave
72737
72737
add a comment |
add a comment |
Here's a similar case as n.m.'s answer.
First, let's have a function, which clears a FooBeforeBase
:
void clearBase(FooBeforeBase *f) {
memset(f, 0, sizeof(*f));
}
This is fine, as clearBase
gets a pointer to FooBeforeBase
, it thinks that as FooBeforeBase
has standard-layout, so memsetting it is safe.
Now, if you do this:
FooBefore b;
b.value = 42;
clearBase(&b);
You don't expect, that clearBase
will clear b.value
, as b.value
is not part of FooBeforeBase
. But, if FooBefore::value
was put into tail-padding of FooBeforeBase
, it would been cleared as well.
Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
No, tail-padding is not required. It is an optimization, which gcc and clang do.
But the standard doesn't allowclearBase
to work on a base class subobject. Well, if we're going to be technical,memset
isn't allowed on TriviallyCopyable types period, but even if that were amemcpy
from a zero-initializedFooBeforeBase
static
instance, it still wouldn't be allowed on base class subobjects.
– Nicol Bolas
Dec 18 '18 at 17:51
@NicolBolas: I nowhere stated that. As a user ofclearBase
, you may not know, what is inside. So, this behavior of the compiler guarantees that you don't shoot yourself in the foot. Please be a little bit more practical here, even if the question has the language-lawyer tag. We already talking about something, which is not covered by the standard (i.e., tail-padding optimization).
– geza
Dec 18 '18 at 17:55
1
"I nowhere stated that". You very much did, right after you said: "Now, if you do this:". That's code which callsclearBase
on a base class subobject, which is UB.
– Nicol Bolas
Dec 18 '18 at 17:56
1
"But the standard doesn't allow clearBase to work" Rather, it doesn't guarantee that it will work.
– n.m.
Dec 18 '18 at 17:57
@NicolBolas: okay then. This behavior is there too guarantee, that even if it is UB, it doesn't do any harm. UB doesn't automatically mean that something bad must happen.
– geza
Dec 18 '18 at 18:01
add a comment |
Here's a similar case as n.m.'s answer.
First, let's have a function, which clears a FooBeforeBase
:
void clearBase(FooBeforeBase *f) {
memset(f, 0, sizeof(*f));
}
This is fine, as clearBase
gets a pointer to FooBeforeBase
, it thinks that as FooBeforeBase
has standard-layout, so memsetting it is safe.
Now, if you do this:
FooBefore b;
b.value = 42;
clearBase(&b);
You don't expect, that clearBase
will clear b.value
, as b.value
is not part of FooBeforeBase
. But, if FooBefore::value
was put into tail-padding of FooBeforeBase
, it would been cleared as well.
Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
No, tail-padding is not required. It is an optimization, which gcc and clang do.
But the standard doesn't allowclearBase
to work on a base class subobject. Well, if we're going to be technical,memset
isn't allowed on TriviallyCopyable types period, but even if that were amemcpy
from a zero-initializedFooBeforeBase
static
instance, it still wouldn't be allowed on base class subobjects.
– Nicol Bolas
Dec 18 '18 at 17:51
@NicolBolas: I nowhere stated that. As a user ofclearBase
, you may not know, what is inside. So, this behavior of the compiler guarantees that you don't shoot yourself in the foot. Please be a little bit more practical here, even if the question has the language-lawyer tag. We already talking about something, which is not covered by the standard (i.e., tail-padding optimization).
– geza
Dec 18 '18 at 17:55
1
"I nowhere stated that". You very much did, right after you said: "Now, if you do this:". That's code which callsclearBase
on a base class subobject, which is UB.
– Nicol Bolas
Dec 18 '18 at 17:56
1
"But the standard doesn't allow clearBase to work" Rather, it doesn't guarantee that it will work.
– n.m.
Dec 18 '18 at 17:57
@NicolBolas: okay then. This behavior is there too guarantee, that even if it is UB, it doesn't do any harm. UB doesn't automatically mean that something bad must happen.
– geza
Dec 18 '18 at 18:01
add a comment |
Here's a similar case as n.m.'s answer.
First, let's have a function, which clears a FooBeforeBase
:
void clearBase(FooBeforeBase *f) {
memset(f, 0, sizeof(*f));
}
This is fine, as clearBase
gets a pointer to FooBeforeBase
, it thinks that as FooBeforeBase
has standard-layout, so memsetting it is safe.
Now, if you do this:
FooBefore b;
b.value = 42;
clearBase(&b);
You don't expect, that clearBase
will clear b.value
, as b.value
is not part of FooBeforeBase
. But, if FooBefore::value
was put into tail-padding of FooBeforeBase
, it would been cleared as well.
Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
No, tail-padding is not required. It is an optimization, which gcc and clang do.
Here's a similar case as n.m.'s answer.
First, let's have a function, which clears a FooBeforeBase
:
void clearBase(FooBeforeBase *f) {
memset(f, 0, sizeof(*f));
}
This is fine, as clearBase
gets a pointer to FooBeforeBase
, it thinks that as FooBeforeBase
has standard-layout, so memsetting it is safe.
Now, if you do this:
FooBefore b;
b.value = 42;
clearBase(&b);
You don't expect, that clearBase
will clear b.value
, as b.value
is not part of FooBeforeBase
. But, if FooBefore::value
was put into tail-padding of FooBeforeBase
, it would been cleared as well.
Is there a required layout per the standard and, if not, why do gcc and clang do what they do?
No, tail-padding is not required. It is an optimization, which gcc and clang do.
answered Dec 18 '18 at 17:49
gezageza
12.8k32775
12.8k32775
But the standard doesn't allowclearBase
to work on a base class subobject. Well, if we're going to be technical,memset
isn't allowed on TriviallyCopyable types period, but even if that were amemcpy
from a zero-initializedFooBeforeBase
static
instance, it still wouldn't be allowed on base class subobjects.
– Nicol Bolas
Dec 18 '18 at 17:51
@NicolBolas: I nowhere stated that. As a user ofclearBase
, you may not know, what is inside. So, this behavior of the compiler guarantees that you don't shoot yourself in the foot. Please be a little bit more practical here, even if the question has the language-lawyer tag. We already talking about something, which is not covered by the standard (i.e., tail-padding optimization).
– geza
Dec 18 '18 at 17:55
1
"I nowhere stated that". You very much did, right after you said: "Now, if you do this:". That's code which callsclearBase
on a base class subobject, which is UB.
– Nicol Bolas
Dec 18 '18 at 17:56
1
"But the standard doesn't allow clearBase to work" Rather, it doesn't guarantee that it will work.
– n.m.
Dec 18 '18 at 17:57
@NicolBolas: okay then. This behavior is there too guarantee, that even if it is UB, it doesn't do any harm. UB doesn't automatically mean that something bad must happen.
– geza
Dec 18 '18 at 18:01
add a comment |
But the standard doesn't allowclearBase
to work on a base class subobject. Well, if we're going to be technical,memset
isn't allowed on TriviallyCopyable types period, but even if that were amemcpy
from a zero-initializedFooBeforeBase
static
instance, it still wouldn't be allowed on base class subobjects.
– Nicol Bolas
Dec 18 '18 at 17:51
@NicolBolas: I nowhere stated that. As a user ofclearBase
, you may not know, what is inside. So, this behavior of the compiler guarantees that you don't shoot yourself in the foot. Please be a little bit more practical here, even if the question has the language-lawyer tag. We already talking about something, which is not covered by the standard (i.e., tail-padding optimization).
– geza
Dec 18 '18 at 17:55
1
"I nowhere stated that". You very much did, right after you said: "Now, if you do this:". That's code which callsclearBase
on a base class subobject, which is UB.
– Nicol Bolas
Dec 18 '18 at 17:56
1
"But the standard doesn't allow clearBase to work" Rather, it doesn't guarantee that it will work.
– n.m.
Dec 18 '18 at 17:57
@NicolBolas: okay then. This behavior is there too guarantee, that even if it is UB, it doesn't do any harm. UB doesn't automatically mean that something bad must happen.
– geza
Dec 18 '18 at 18:01
But the standard doesn't allow
clearBase
to work on a base class subobject. Well, if we're going to be technical, memset
isn't allowed on TriviallyCopyable types period, but even if that were a memcpy
from a zero-initialized FooBeforeBase
static
instance, it still wouldn't be allowed on base class subobjects.– Nicol Bolas
Dec 18 '18 at 17:51
But the standard doesn't allow
clearBase
to work on a base class subobject. Well, if we're going to be technical, memset
isn't allowed on TriviallyCopyable types period, but even if that were a memcpy
from a zero-initialized FooBeforeBase
static
instance, it still wouldn't be allowed on base class subobjects.– Nicol Bolas
Dec 18 '18 at 17:51
@NicolBolas: I nowhere stated that. As a user of
clearBase
, you may not know, what is inside. So, this behavior of the compiler guarantees that you don't shoot yourself in the foot. Please be a little bit more practical here, even if the question has the language-lawyer tag. We already talking about something, which is not covered by the standard (i.e., tail-padding optimization).– geza
Dec 18 '18 at 17:55
@NicolBolas: I nowhere stated that. As a user of
clearBase
, you may not know, what is inside. So, this behavior of the compiler guarantees that you don't shoot yourself in the foot. Please be a little bit more practical here, even if the question has the language-lawyer tag. We already talking about something, which is not covered by the standard (i.e., tail-padding optimization).– geza
Dec 18 '18 at 17:55
1
1
"I nowhere stated that". You very much did, right after you said: "Now, if you do this:". That's code which calls
clearBase
on a base class subobject, which is UB.– Nicol Bolas
Dec 18 '18 at 17:56
"I nowhere stated that". You very much did, right after you said: "Now, if you do this:". That's code which calls
clearBase
on a base class subobject, which is UB.– Nicol Bolas
Dec 18 '18 at 17:56
1
1
"But the standard doesn't allow clearBase to work" Rather, it doesn't guarantee that it will work.
– n.m.
Dec 18 '18 at 17:57
"But the standard doesn't allow clearBase to work" Rather, it doesn't guarantee that it will work.
– n.m.
Dec 18 '18 at 17:57
@NicolBolas: okay then. This behavior is there too guarantee, that even if it is UB, it doesn't do any harm. UB doesn't automatically mean that something bad must happen.
– geza
Dec 18 '18 at 18:01
@NicolBolas: okay then. This behavior is there too guarantee, that even if it is UB, it doesn't do any harm. UB doesn't automatically mean that something bad must happen.
– geza
Dec 18 '18 at 18:01
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%2f53837373%2fstandard-layout-and-tail-padding%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
Who says that this behavior is "required" by anything in the standard? It could simply be a manifestation of how the compiler goes about implementing things.
– Nicol Bolas
Dec 18 '18 at 17:23
@NicolBolas It may very well not be required. MSVC does not do this (its
FooAfter
is also 24 bytes), but gcc and clang do - and it seems like that's a conscious choice on their parts.– Barry
Dec 18 '18 at 17:32
"it seems like that's a conscious choice on their parts." What makes you say that?
– Nicol Bolas
Dec 18 '18 at 17:33
1
Related: stackoverflow.com/a/51334730/775806
– n.m.
Dec 18 '18 at 17:49
It is never required that there is no padding between class members. It may be required that there is padding. So the correct question is not which part of the standard requires gcc to reuse the end-padding, but what allows it to do so in the second case. Another question is whether something disallows such reuse in the first case.
– n.m.
Dec 18 '18 at 17:53