Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Question on padding and alignment of `struct` and its members

152 views
Skip to first unread message

Pankaj Jangid

unread,
Sep 26, 2020, 4:09:32 AM9/26/20
to
Following snippet is copied from
https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=vs-2019

--8<---------------cut here---------------start------------->8---
// Shows the actual memory layout
struct x_
{
char a; // 1 byte
char _pad0[3]; // padding to put 'b' on 4-byte boundary
int b; // 4 bytes
short c; // 2 bytes
char d; // 1 byte
char _pad1[1]; // padding to make sizeof(x_) multiple of 4
} bar[3];
--8<---------------cut here---------------end--------------->8---

In the above code snippet, is my following understanding correct?

`char _pad0[3]` as explained is added to put `b` on 4-byte boundary. A
similar padding is not added before `short c` because it is already on
2-byte boundary (sizeof(short)). Similarly for `char d`. Each element of
the struct is given an aligned address. Right?

Regards

--
Pankaj Jangid

Christian Hanné

unread,
Sep 26, 2020, 4:13:46 AM9/26/20
to
Yes, you can rely for the same behaviour on almost any platfom.

Ian Collins

unread,
Sep 26, 2020, 5:01:40 AM9/26/20
to
If in doubt, add a static_assert...

--
Ian.

Pankaj Jangid

unread,
Sep 26, 2020, 5:30:21 AM9/26/20
to
On Sat, Sep 26 2020, Christian Hanné wrote:
> Yes, you can rely for the same behaviour on almost any platfom.

Thanks

--
Pankaj Jangid

Pankaj Jangid

unread,
Sep 26, 2020, 5:34:06 AM9/26/20
to
On Sat, Sep 26 2020, Ian Collins wrote:

>> In the above code snippet, is my following understanding correct?
>> `char _pad0[3]` as explained is added to put `b` on 4-byte
>> boundary. A similar padding is not added before `short c` because it
>> is already on 2-byte boundary (sizeof(short)). Similarly for `char
>> d`. Each element of the struct is given an aligned address. Right?
>
> If in doubt, add a static_assert...

My question was about alignment but now I have another query.

`static_assert` works during compile time. First param is a constant
expression. And memory allocation is a thing of runtime. Probably this
cannot be used at all times.

--
Pankaj Jangid

Richard Damon

unread,
Sep 26, 2020, 6:29:17 AM9/26/20
to
You can write a static assert using the sizeof the structure or offsetof
a member, which are static compile time operators.

Jorgen Grahn

unread,
Sep 26, 2020, 7:18:40 AM9/26/20
to
On Sat, 2020-09-26, Pankaj Jangid wrote:
> Following snippet is copied from
> https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=vs-2019
>
> --8<---------------cut here---------------start------------->8---
> // Shows the actual memory layout
> struct x_
> {
> char a; // 1 byte
> char _pad0[3]; // padding to put 'b' on 4-byte boundary
> int b; // 4 bytes
> short c; // 2 bytes
> char d; // 1 byte
> char _pad1[1]; // padding to make sizeof(x_) multiple of 4
> } bar[3];
> --8<---------------cut here---------------end--------------->8---

Note to readers who didn't follow the link: this isn't C++ code that
Microsoft /recommends/ for any purpose; it illustrates how their
compiler would would lay out an array of {a, b, c, d} structs in
memory. It's pseudocode with C++ syntax.

> In the above code snippet, is my following understanding correct?
>
> `char _pad0[3]` as explained is added to put `b` on 4-byte boundary. A
> similar padding is not added before `short c` because it is already on
> 2-byte boundary (sizeof(short)). Similarly for `char d`. Each element of
> the struct is given an aligned address. Right?

Note that this is normally nothing you have to think about.

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Pankaj Jangid

unread,
Sep 26, 2020, 7:36:55 AM9/26/20
to
Yes. For my original use-case it will work.

Thanks.

--
Pankaj Jangid

Öö Tiib

unread,
Sep 26, 2020, 9:04:18 AM9/26/20
to
In my work we use those types rarely, since precise byte- and even
bit-widths tend to matter. We use usually uint8_t, int16_t, int32_t.
Where platform does not support these then the code does not compile
and so there are no need to add static_assert checking what bit
width is int. The only inconvenience with those are portable printf
format specifiers that look butt ugly.

olcott

unread,
Sep 26, 2020, 1:11:02 PM9/26/20
to
On 9/26/2020 6:18 AM, Jorgen Grahn wrote:
> struct x_
> {
> char a; // 1 byte
> char _pad0[3]; // padding to put 'b' on 4-byte boundary
> int b; // 4 bytes
> short c; // 2 bytes
> char d; // 1 byte
> char_pad1[1]; // padding to make sizeof(x_) multiple of 4
> } bar[3];


It might make more sense to take alignment into account when defining
the struct:

struct x_
{
int b; // 4 bytes
short c; // 2 bytes
char a; // 1 byte
char d; // 1 byte
} bar[3];

--
Copyright 2020 Pete Olcott

Pankaj Jangid

unread,
Sep 27, 2020, 5:58:52 AM9/27/20
to
On Sat, Sep 26 2020, olcott wrote:

>> struct x_
>> {
>> char a; // 1 byte
>> char _pad0[3]; // padding to put 'b' on 4-byte boundary
>> int b; // 4 bytes
>> short c; // 2 bytes
>> char d; // 1 byte
>> char_pad1[1]; // padding to make sizeof(x_) multiple of 4
>> } bar[3];
>
>
> It might make more sense to take alignment into account when defining
> the struct:
>
> struct x_
> {
> int b; // 4 bytes
> short c; // 2 bytes
> char a; // 1 byte
> char d; // 1 byte
> } bar[3];

Certainly. But I have one more query in this regard. This clearly saves
memory. But is there an effect on speed as well? Ultimately, the
compiler aligns it; so I guess there is no effect on speed.

David Brown

unread,
Sep 27, 2020, 7:29:40 AM9/27/20
to
That will depend on what you are doing with the struct. If you have a
single item, the size won't make a difference. If you are copying lots
of them, size certainly /does/ matter. If you are passing a "struct _x"
in a function, perhaps the 64-bit version will be passed in a register
rather than a block of memory on the stack, for significant speed gains.
If you have enough of them, the smaller version gets better cache hit
ratios and is thus faster.

In general, smaller is faster for data. But whether or not it is
/significantly/ faster is a different matter.

Öö Tiib

unread,
Sep 27, 2020, 11:13:56 AM9/27/20
to
1/3 size economy will improve also some speed as you can exchange 1.5
times more records at same throughput. But usually the overall effect
is way less.

olcott

unread,
Sep 27, 2020, 12:59:45 PM9/27/20
to
If you want maximum speed you have to waste space and define char as
int. I think that the compiler might load "d" as an int and then & out
the upper bits. Same idea with "a" plus bit shifting.

olcott

unread,
Sep 27, 2020, 1:43:58 PM9/27/20
to
I take this back. The memory fetch operation is much slower than
internal processor operations. I am not sure of the speed cache fetch
compared to internal processor operations. Because of this the minimum
space approach may also be the maximum speed approach.

Vir Campestris

unread,
Sep 27, 2020, 4:13:15 PM9/27/20
to
... and following on to this a little late:
Pankaj, be aware of that
>> Yes, you can rely for the same behaviour on almost any platfom.
From Christian. He means it. Almost any. Most of the time...

An int could be 64 bits, even though it's almost always 32 these days.
On some small systems I think it's still 16 the way it used to be.

There's also #pragma pack which affects the padding inserted.

Andy

olcott

unread,
Sep 27, 2020, 5:42:51 PM9/27/20
to
I just found out that different compilers might handle padding
differently. I had some code that parsed COFF object files that depended
on known alignment. It turned out in this case that fixed width integers
solved the problem.

https://en.cppreference.com/w/cpp/types/integer
I didn't need any of the structure packing directives like #pragma pack

Pankaj Jangid

unread,
Sep 27, 2020, 10:15:18 PM9/27/20
to
On Sun, Sep 27 2020, olcott wrote:

>> If you want maximum speed you have to waste space and define char as
>> int. I think that the compiler might load "d" as an int and then &
>> out the upper bits. Same idea with "a" plus bit shifting.
>>
>
> I take this back. The memory fetch operation is much slower than
> internal processor operations. I am not sure of the speed cache fetch
> compared to internal processor operations. Because of this the minimum
> space approach may also be the maximum speed approach.

I was thinking on exactly these lines after getting followup posts from
other participants. Thanks for clarifying.
--
Pankaj Jangid

Juha Nieminen

unread,
Sep 28, 2020, 5:42:17 AM9/28/20
to
Pankaj Jangid <pankaj...@gmail.com> wrote:
>> It might make more sense to take alignment into account when defining
>> the struct:
>>
>> struct x_
>> {
>> int b; // 4 bytes
>> short c; // 2 bytes
>> char a; // 1 byte
>> char d; // 1 byte
>> } bar[3];
>
> Certainly. But I have one more query in this regard. This clearly saves
> memory. But is there an effect on speed as well? Ultimately, the
> compiler aligns it; so I guess there is no effect on speed.

As long as the elements don't cross word boundaries, there shouldn't be
an effect on speed.

(On x86 architectures reading a value that crosses word boundaries might
incur a penalty, which may be several clock cycles long. On some other
architectures it's not possible at all, as doing so would cause a CPU
interrupt. If the compiler deliberately packed members in such a way that
they cross word boundaries, it then needs to always add code that reads
and writes these values in parts, in a manner that never crosses the
word boundary, which of course incurs a speed penalty because of additional
code being executed.)

The struct taking less space might give a theoretical speed advantage
in that being smaller means that it consumes less cache space (and thus
more other data, including other instances of this same struct, will fit
in the cache.)

olcott

unread,
Sep 28, 2020, 11:46:18 AM9/28/20
to
A Cache hit with a smaller struct would be much faster then a cache miss
even if the smaller struct requires extra bitwise operations to separate
to the less than word sized fields.

Scott Lurndal

unread,
Sep 28, 2020, 12:44:42 PM9/28/20
to
Pankaj Jangid <pankaj...@gmail.com> writes:
>On Sun, Sep 27 2020, olcott wrote:
>
>>> If you want maximum speed you have to waste space and define char as
>>> int. I think that the compiler might load "d" as an int and then &
>>> out the upper bits. Same idea with "a" plus bit shifting.
>>>
>>
>> I take this back. The memory fetch operation is much slower than
>> internal processor operations. I am not sure of the speed cache fetch
>> compared to internal processor operations. Because of this the minimum
>> space approach may also be the maximum speed approach.

A typical modern cache has a load-to-use latency of between
three and four cycles for a hit in the L1I or L1D cache.

With a sufficiently long pipeline and out-of-order execution, that
latency will be filled with other operations.

Latency to second level cache and last level caches can be up to
20ns, and to dram 65-100+ns.

Scott Lurndal

unread,
Sep 28, 2020, 12:50:41 PM9/28/20
to
Juha Nieminen <nos...@thanks.invalid> writes:
>Pankaj Jangid <pankaj...@gmail.com> wrote:
>>> It might make more sense to take alignment into account when defining
>>> the struct:
>>>
>>> struct x_
>>> {
>>> int b; // 4 bytes
>>> short c; // 2 bytes
>>> char a; // 1 byte
>>> char d; // 1 byte
>>> } bar[3];
>>
>> Certainly. But I have one more query in this regard. This clearly saves
>> memory. But is there an effect on speed as well? Ultimately, the
>> compiler aligns it; so I guess there is no effect on speed.
>
>As long as the elements don't cross word boundaries, there shouldn't be
>an effect on speed.

Assuming natural alignment.

>
>(On x86 architectures reading a value that crosses word boundaries might
>incur a penalty, which may be several clock cycles long. On some other
>architectures it's not possible at all, as doing so would cause a CPU
>interrupt.

Even on x86, it might cause a page fault if the word straddles
a page boundary (permission or not-present fault) orif the
Alignment Flag is not set in the processor flags.

> If the compiler deliberately packed members in such a way that
>they cross word boundaries, it then needs to always add code that reads
>and writes these values in parts, in a manner that never crosses the
>word boundary, which of course incurs a speed penalty because of additional
>code being executed.)

x86 (and ARM64 for compatability with ported x86 apps) processors are generally
configured to support unaligned accesses (but both can be configured to
fault on unaligned accesses). Depending on the processor,
there may be a one-cycle penalty for barrel shifter.

Unaligned accesses to uncachable memory or device memory on both X86 and ARM64
will fault.

>
>The struct taking less space might give a theoretical speed advantage
>in that being smaller means that it consumes less cache space (and thus
>more other data, including other instances of this same struct, will fit
>in the cache.)

Indeed, and for large applications, that can make a noticable difference.

olcott

unread,
Sep 28, 2020, 1:05:50 PM9/28/20
to
Thanks for the actual clock cycle numbers, I could not find these on a
Google search. This seems to confirm my estimate that manually arranging
the fields of a struct to minimize the need for padding would be a
really good idea.

Although compilers could be smart enough to do this, they must refrain
just in case the order of the fields must correpond to disk storage.

Keith Thompson

unread,
Sep 28, 2020, 2:07:47 PM9/28/20
to
Juha Nieminen <nos...@thanks.invalid> writes:
> Pankaj Jangid <pankaj...@gmail.com> wrote:
>>> It might make more sense to take alignment into account when defining
>>> the struct:
>>>
>>> struct x_
>>> {
>>> int b; // 4 bytes
>>> short c; // 2 bytes
>>> char a; // 1 byte
>>> char d; // 1 byte
>>> } bar[3];
>>
>> Certainly. But I have one more query in this regard. This clearly saves
>> memory. But is there an effect on speed as well? Ultimately, the
>> compiler aligns it; so I guess there is no effect on speed.
>
> As long as the elements don't cross word boundaries, there shouldn't be
> an effect on speed.
>
> (On x86 architectures reading a value that crosses word boundaries might
> incur a penalty, which may be several clock cycles long. On some other
> architectures it's not possible at all, as doing so would cause a CPU
> interrupt. If the compiler deliberately packed members in such a way that
> they cross word boundaries, it then needs to always add code that reads
> and writes these values in parts, in a manner that never crosses the
> word boundary, which of course incurs a speed penalty because of additional
> code being executed.)

It's not always possible for the compiler to know that it needs to do
that. If you take the address of a misaligned member, code that deals
with the resulting pointer doesn't know that it's misaligned. That's
not much of an issue in x86, where misaligned accesses (usually?) just
impose a speed penalty, but it could be a problem on architectures that
impose strict alignment requirements.

More information:
https://stackoverflow.com/q/8568432/827263
https://stackoverflow.com/a/8568441/827263

> The struct taking less space might give a theoretical speed advantage
> in that being smaller means that it consumes less cache space (and thus
> more other data, including other instances of this same struct, will fit
> in the cache.)

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for Philips Healthcare
void Void(void) { Void(); } /* The recursive call of the void */

olcott

unread,
Sep 28, 2020, 2:32:08 PM9/28/20
to
All that I had to do to optimize the need for padding of the reference
struct is simply sort the members in size order from largest to
smallest. Certainly every compiler could compare the need for padding of
the specified version with the sorted version and then know whether or
not sorting reduces padding requirements.

> If you take the address of a misaligned member, code that deals
> with the resulting pointer doesn't know that it's misaligned. That's

If we sort every member by largest to smallest size order then to
provide perfectly aligned access to data members might at most require
three bytes of padding at the end. The compiler can certainly be smart
enough to know that it needs to do some bitwise operations to isolate
the required field when two or more fields are loaded by one aligned
memory access.

The problem with the compiler doing this is when the struct must conform
to its disk storage positions. I ran into this issue reccently when I
needed both Windows and Linux to parse COFF object files correctly.

> not much of an issue in x86, where misaligned accesses (usually?) just
> impose a speed penalty, but it could be a problem on architectures that
> impose strict alignment requirements.
>
> More information:
> https://stackoverflow.com/q/8568432/827263
> https://stackoverflow.com/a/8568441/827263
>
>> The struct taking less space might give a theoretical speed advantage
>> in that being smaller means that it consumes less cache space (and thus
>> more other data, including other instances of this same struct, will fit
>> in the cache.)
>


--
Copyright 2020 Pete Olcott

Vir Campestris

unread,
Sep 28, 2020, 4:13:03 PM9/28/20
to
On 28/09/2020 19:31, olcott wrote:
> If we sort every member by largest to smallest size order then to
> provide perfectly aligned access to data members might at most require
> three bytes of padding at the end. The compiler can certainly be smart
> enough to know that it needs to do some bitwise operations to isolate
> the required field when two or more fields are loaded by one aligned
> memory access.

There's no guarantee how much padding you'll get at the end if these
objects are allocated individually on the heap, rather than in an array.

There are all sorts of clever heap allocation routines.
One common way is to round all requests up to a multiple of some
smallish size, for example 16 bytes.

Another common way is to have a series of pools for items of different
sizes, and give you a block from the pool that has big enough blocks.
These blocks can be quite large, so you might find your 1025-byte
structure gets allocated from the 2048 byte pool. These pools help avoid
fragmentation, but there's a cost.

Finally although it's correct that ordering them largest-to-smallest
might be most efficient in space terms the sanity of the programmer can
also be important. Not all structures are used by performance critical code!

Andy

olcott

unread,
Sep 28, 2020, 6:02:38 PM9/28/20
to
Yes that last point is most important. If the struct has less than six
members order is not crucial for sufficiently readable code.

If the struct has hundreds of members it is best to put related members
together for much more readable code.

Chris M. Thomasson

unread,
Sep 28, 2020, 8:45:52 PM9/28/20
to
Iirc, misaligned access with a LOCK prefix issues a full bus lock
instead of locking a cache line. Check this out:

https://blogs.oracle.com/dave/qpi-quiescence

Richard Damon

unread,
Sep 28, 2020, 10:35:36 PM9/28/20
to
On 9/28/20 2:31 PM, olcott wrote:
>
> All that I had to do to optimize the need for padding of the reference
> struct is simply sort the members in size order from largest to
> smallest. Certainly every compiler could compare the need for padding of
> the specified version with the sorted version and then know whether or
> not sorting reduces padding requirements.

I believe the standard requires that the order of the members in the
struct has to match the order they are declared in the struct, at least
as long as a access specifier doesn't exist between them.

That rule comes from C, without the exception since C doesn't have
access specifiers. As you alluded to elsewhere, allowing rearangement
can cause all sorts of issues with code that makes some otherwise fairly
safe assumptions. I suspect that the rule came out because the early
compilers were smart enough to rearrange, then a lot of code was built
with that assumption, and it became effectively impossible to safely
rearrange so it was defined that it couldn't.

Keith Thompson

unread,
Sep 29, 2020, 1:04:22 AM9/29/20
to
Richard Damon <Ric...@Damon-Family.org> writes:
> On 9/28/20 2:31 PM, olcott wrote:
>>
>> All that I had to do to optimize the need for padding of the reference
>> struct is simply sort the members in size order from largest to
>> smallest. Certainly every compiler could compare the need for padding of
>> the specified version with the sorted version and then know whether or
>> not sorting reduces padding requirements.
>
> I believe the standard requires that the order of the members in the
> struct has to match the order they are declared in the struct, at least
> as long as a access specifier doesn't exist between them.
>
> That rule comes from C, without the exception since C doesn't have
> access specifiers. As you alluded to elsewhere, allowing rearangement
> can cause all sorts of issues with code that makes some otherwise fairly
> safe assumptions. I suspect that the rule came out because the early
> compilers were smart enough to rearrange, then a lot of code was built

Did you mean *weren't* smart enough to rearrange?

> with that assumption, and it became effectively impossible to safely
> rearrange so it was defined that it couldn't.

olcott

unread,
Sep 29, 2020, 1:34:38 AM9/29/20
to
On 9/29/2020 12:04 AM, Keith Thompson wrote:
> Richard Damon <Ric...@Damon-Family.org> writes:
>> On 9/28/20 2:31 PM, olcott wrote:
>>>
>>> All that I had to do to optimize the need for padding of the reference
>>> struct is simply sort the members in size order from largest to
>>> smallest. Certainly every compiler could compare the need for padding of
>>> the specified version with the sorted version and then know whether or
>>> not sorting reduces padding requirements.
>>
>> I believe the standard requires that the order of the members in the
>> struct has to match the order they are declared in the struct, at least
>> as long as a access specifier doesn't exist between them.
>>
>> That rule comes from C, without the exception since C doesn't have
>> access specifiers. As you alluded to elsewhere, allowing rearangement
>> can cause all sorts of issues with code that makes some otherwise fairly
>> safe assumptions. I suspect that the rule came out because the early
>> compilers were smart enough to rearrange, then a lot of code was built
>
> Did you mean *weren't* smart enough to rearrange?

No he meant "were" smart enough, yet this caused problems so they had to
make a standard. It doesn't take much intelligence to sort fields by
size and add a few padding bytes at the end. They (apparently) made it a
standard to not change the specified order to have cross compiler
consistency.

>
>> with that assumption, and it became effectively impossible to safely
>> rearrange so it was defined that it couldn't.
>


--
Copyright 2020 Pete Olcott

Jorgen Grahn

unread,
Sep 29, 2020, 1:53:04 AM9/29/20
to
On Tue, 2020-09-29, Keith Thompson wrote:
> Richard Damon <Ric...@Damon-Family.org> writes:
>> On 9/28/20 2:31 PM, olcott wrote:
>>>
>>> All that I had to do to optimize the need for padding of the reference
>>> struct is simply sort the members in size order from largest to
>>> smallest. Certainly every compiler could compare the need for padding of
>>> the specified version with the sorted version and then know whether or
>>> not sorting reduces padding requirements.
>>
>> I believe the standard requires that the order of the members in the
>> struct has to match the order they are declared in the struct, at least
>> as long as a access specifier doesn't exist between them.
>>
>> That rule comes from C, without the exception since C doesn't have
>> access specifiers. As you alluded to elsewhere, allowing rearangement
>> can cause all sorts of issues with code that makes some otherwise fairly
>> safe assumptions. I suspect that the rule came out because the early
>> compilers were smart enough to rearrange, then a lot of code was built
>
> Did you mean *weren't* smart enough to rearrange?

He must have.

>> with that assumption, and it became effectively impossible to safely
>> rearrange so it was defined that it couldn't.

If that's what happened, it's a really bad combination: letting bad
code prevent important optimizations. I can see no reason for
portable code to know the struct layout (except perhaps that you can
cast between &foo and &foo.first_element).

One could easily imagine each ABI defining struct layout, with
rearrangements, in such a way that waste is minimized.

olcott

unread,
Sep 29, 2020, 2:00:21 AM9/29/20
to
It makes sense to have the compilers conform to a consistent standard
across compilers.

Öö Tiib

unread,
Sep 29, 2020, 2:24:09 AM9/29/20
to
Clang's -Wpadded warns that there is padding but for some reason fails
to make difference if reordering would help with something or
would just put same padding at end of struct. So even very modern
compiler can easily be relatively dim-witted about the issue.

Tim Woodall

unread,
Sep 29, 2020, 2:40:24 AM9/29/20
to
On 2020-09-28, olcott <No...@NoWhere.com> wrote:
>
> If we sort every member by largest to smallest size order then to
> provide perfectly aligned access to data members might at most require
> three bytes of padding at the end. The compiler can certainly be smart
> enough to know that it needs to do some bitwise operations to isolate
> the required field when two or more fields are loaded by one aligned
> memory access.
>

What about a struct of array members?

struct {
short[3]
short
short[3]
short
char[3]
char
char[3]
char
};

The Bin Packing Problem is NP-hard. ISTM that the alignment constraints
effectively make optimizing the arrangement of members in the struct an
equivalent problem.

David Brown

unread,
Sep 29, 2020, 3:58:19 AM9/29/20
to
Compilers /can/ re-arrange struct fields - as long as it does not affect
the observable behaviour of the code. (Writing the struct directly out
to a file would be observable behaviour.) For a while, gcc had an
optimisation that did such re-arrangements, but it got removed as it was
overly complex and unmaintainable in the face of link-time optimisation.
For local structs within a function, compilers certainly can and do
break up and re-arrange fields.


Öö Tiib

unread,
Sep 29, 2020, 7:49:29 AM9/29/20
to
The problem is that there are two orthogonal considerations: 1) member
order in memory and 2) member initialisation order. Both are expressed
with declaration order in C++. When optimal of one differs from optimal
of other then we do not have semantics to express it.

Other a nit ... C++ standard says that members with same access control
are ordered by declaration order (specifiers do not necessarily change
access control).

Richard Damon

unread,
Sep 29, 2020, 8:15:17 AM9/29/20
to
On 9/29/20 1:04 AM, Keith Thompson wrote:
> Richard Damon <Ric...@Damon-Family.org> writes:
>> On 9/28/20 2:31 PM, olcott wrote:
>>>
>>> All that I had to do to optimize the need for padding of the reference
>>> struct is simply sort the members in size order from largest to
>>> smallest. Certainly every compiler could compare the need for padding of
>>> the specified version with the sorted version and then know whether or
>>> not sorting reduces padding requirements.
>>
>> I believe the standard requires that the order of the members in the
>> struct has to match the order they are declared in the struct, at least
>> as long as a access specifier doesn't exist between them.
>>
>> That rule comes from C, without the exception since C doesn't have
>> access specifiers. As you alluded to elsewhere, allowing rearangement
>> can cause all sorts of issues with code that makes some otherwise fairly
>> safe assumptions. I suspect that the rule came out because the early
>> compilers were smart enough to rearrange, then a lot of code was built
>
> Did you mean *weren't* smart enough to rearrange?
Yes.

Richard Damon

unread,
Sep 29, 2020, 8:28:43 AM9/29/20
to
The first big problem is that if you reorder, the reordering MUST be
consistent in every compilation unit, and ideally you want different
compilers to come up with the same result for the same machine.

Second, remember that most C code, especially early on want designed to
be totally portable, just fairly portable. It was very common that piece
of most programs would have a lot of assumptions on the machine that was
targeted. If you were doing it well, you tried to isolate most of these
assumptions to just a few files marked with portability comments.
Structs WERE often used to map to hardware registers or file formats,
and the programmer would maybe need to adjust things to map to their
hardware. (Remember, fixed width types are sort of new). Sometimes,
particularly for file (or other communication) formats, you would
realize that your hardware didn't map well to what the format was based
on, and you needed to add a layer to convert to raw bytes (endian issues
or misaligned fields being the biggest example). If compilers could
re-arrange structs, then the odds of being able to match a struct to an
externally defined format drops significantly, forcing going to the low
level techniques.

olcott

unread,
Sep 29, 2020, 11:52:17 AM9/29/20
to
No, apparently not at all. Standards complying compilers are not allowed
to reorder the fields.

olcott

unread,
Sep 29, 2020, 11:57:19 AM9/29/20
to
On 9/29/2020 1:33 AM, Tim Woodall wrote:
> On 2020-09-28, olcott <No...@NoWhere.com> wrote:
>>
>> If we sort every member by largest to smallest size order then to
>> provide perfectly aligned access to data members might at most require
>> three bytes of padding at the end. The compiler can certainly be smart
>> enough to know that it needs to do some bitwise operations to isolate
>> the required field when two or more fields are loaded by one aligned
>> memory access.
>>
>
> What about a struct of array members?
>
> struct {
> short[3]
> short
> short[3]
> short
> char[3]
> char
> char[3]
> char
> };
>

If we simply assume that the compiler always loads a 32-bit int and then
does bitwise operations to separate the required field the above struct
is already fully packed.

> The Bin Packing Problem is NP-hard. ISTM that the alignment constraints
> effectively make optimizing the arrangement of members in the struct an
> equivalent problem.
>

Try and find a concrete example where simply sorting by size and padding
at the end won't work.

olcott

unread,
Sep 29, 2020, 12:01:00 PM9/29/20
to
On 9/29/2020 6:49 AM, Öö Tiib wrote:
> On Tuesday, 29 September 2020 05:35:36 UTC+3, Richard Damon wrote:
>> On 9/28/20 2:31 PM, olcott wrote:
>>>
>>> All that I had to do to optimize the need for padding of the reference
>>> struct is simply sort the members in size order from largest to
>>> smallest. Certainly every compiler could compare the need for padding of
>>> the specified version with the sorted version and then know whether or
>>> not sorting reduces padding requirements.
>>
>> I believe the standard requires that the order of the members in the
>> struct has to match the order they are declared in the struct, at least
>> as long as a access specifier doesn't exist between them.
>>
>> That rule comes from C, without the exception since C doesn't have
>> access specifiers. As you alluded to elsewhere, allowing rearangement
>> can cause all sorts of issues with code that makes some otherwise fairly
>> safe assumptions. I suspect that the rule came out because the early
>> compilers were smart enough to rearrange, then a lot of code was built
>> with that assumption, and it became effectively impossible to safely
>> rearrange so it was defined that it couldn't.
>
> The problem is that there are two orthogonal considerations: 1) member
> order in memory and 2) member initialisation order. Both are expressed
> with declaration order in C++. When optimal of one differs from optimal
> of other then we do not have semantics to express it.
>

Sure we do. The initialization order can be specified in the contructor.

> Other a nit ... C++ standard says that members with same access control
> are ordered by declaration order (specifiers do not necessarily change
> access control).
>


Juha Nieminen

unread,
Sep 29, 2020, 12:05:43 PM9/29/20
to
olcott <No...@nowhere.com> wrote:
> All that I had to do to optimize the need for padding of the reference
> struct is simply sort the members in size order from largest to
> smallest. Certainly every compiler could compare the need for padding of
> the specified version with the sorted version and then know whether or
> not sorting reduces padding requirements.

Btw, this is the reason why I was so disappointed that C++20 designated
initializers have to be specified in the same order as the members have
been declared.

In some situations you may want to declare the members in an order that
optimizes space, but specify them in the initialization list in a more
logical order (especially if it's some kind of struct containing all
kinds of settings or other parameters).

Also, if you ever eg. change the size of a member and thus need to
change its placement inside the struct, all the initializations will
break (even though that's supposed to be one of the biggest advantages
of designated initializers: The initialization doesn't break if the
order of the members changes.) I suppose getting error messages is
better than the wrong elements being silently initialized, but still,
it would be niced if it just worked.

Juha Nieminen

unread,
Sep 29, 2020, 12:21:52 PM9/29/20
to
Tim Woodall <new...@woodall.me.uk> wrote:
> What about a struct of array members?
>
> struct {
> short[3]
> short
> short[3]
> short
> char[3]
> char
> char[3]
> char
> };
>
> The Bin Packing Problem is NP-hard. ISTM that the alignment constraints
> effectively make optimizing the arrangement of members in the struct an
> equivalent problem.

AFAIK the one-dimensional version (which this situation is) is not NP.
You would need at least two structs, and the problem being to distribute
the given elements optimally among them.

Alignment constraints effectively make it into a "multiple containers"
situation, because you need to distribute all the elements among these
containing ranges without them spilling over... except it's still not
NP because the elements are always exactly a half, a quarter and
possibly an eighth of the container size.

In this situation you can simply always take the largest remaining
element that will fit in the current free space of the current slot.
(There's no optimization conundrum because of that restriction above.)

Paavo Helde

unread,
Sep 29, 2020, 3:08:59 PM9/29/20
to
29.09.2020 19:00 olcott kirjutas:
> On 9/29/2020 6:49 AM, Öö Tiib wrote:

>> The problem is that there are two orthogonal considerations: 1) member
>> order in memory and 2) member initialisation order. Both are expressed
>> with declaration order in C++. When optimal of one differs from optimal
>> of other then we do not have semantics to express it.
>>
>
> Sure we do. The initialization order can be specified in the contructor.

Sorry, but no. The standard says in [class.base.init]: "Then, non-static
data members are initialized in the order they were declared in the
class definition (again regardless of the order of the mem-initializers)."

James Kuyper

unread,
Sep 29, 2020, 3:29:10 PM9/29/20
to
Which is immediately followed by "Finally, the compound-statement of the
constructor body is executed." - I presume that he was referring to
initialization occurring inside that compound-statement, which can, in
fact, initialize the members in any desired order.

olcott

unread,
Sep 29, 2020, 3:44:36 PM9/29/20
to
There is a semantic difference between initializers and constructors.

struct Construct
{
int X;
int Y;
int Z;
Construct();
};


Construct::Construct()
{
printf("Construct::Construct()\n");
Y = 55;
Z = 66;
X = 77;

Jorgen Grahn

unread,
Sep 29, 2020, 4:39:55 PM9/29/20
to
I thought that was what I addressed in my last paragraph above:

>> One could easily imagine each ABI defining struct layout, with
>> rearrangements, in such a way that waste is minimized.

E.g. a stable sort of the struct members by size would avoid a lot of
padding, while still being predictable.

> Second, remember that most C code, especially early on want designed to
^^^^
I assume that should read ", wasn't".

> be totally portable, just fairly portable. It was very common that piece
> of most programs would have a lot of assumptions on the machine that was
> targeted. If you were doing it well, you tried to isolate most of these
> assumptions to just a few files marked with portability comments.
> Structs WERE often used to map to hardware registers or file formats,
> and the programmer would maybe need to adjust things to map to their
> hardware. (Remember, fixed width types are sort of new). Sometimes,
> particularly for file (or other communication) formats, you would
> realize that your hardware didn't map well to what the format was based
> on, and you needed to add a layer to convert to raw bytes (endian issues
> or misaligned fields being the biggest example).

I do remember all that, and that's what I feel is "letting bad code
prevent important optimizations". I would have preferred if the
language allowed an ABI to optimize struct layout. People who wanted
to use structs as a convenient way to access bytes in memory could
either (a) know the ABI or (b) use some kind of "packed structs"
extension, like they mostly do today.

It's not clear to me as I write this whether the language or the
popular ABIs prevent this ... but I don't really have to know which
one.

> If compilers could
> re-arrange structs, then the odds of being able to match a struct to an
> externally defined format drops significantly, forcing going to the low
> level techniques.

I should probably add that I feel mapping structs onto raw memory is
the /real/ low level technique. I always try to do such things with
portable code, e.g. parsing binary file formats one octet at a time.
That may be a bit slower (I have never tried measuring) but it's portable
and I think it invites fewer bugs too. Including fewer security bugs.

David Brown

unread,
Sep 29, 2020, 4:49:10 PM9/29/20
to
Compilers are not allowed to make /visible/ reordering of the fields.
But compilers are always allowed to do whatever they want in the way of
optimisations as long as the code appears "as if" it followed the
language rules literally.

David Brown

unread,
Sep 29, 2020, 4:52:08 PM9/29/20
to
And as always, the compiler can re-arrange that into any order it wants
if the difference can't be seen by code. (That applies to both the
member initialisation part, and the constructor body.)

David Brown

unread,
Sep 29, 2020, 4:54:33 PM9/29/20
to
The compiler can implement that as though you had written:

Construct::Construct()
{
X = 77;
Y = 55;
Z = 66;
printf("Construct::Construct()\n");
}

In this case, there is no semantic difference because there is no
observable difference in the behaviour of the program for different
orderings of the initialisers or even the printf() call.


Paavo Helde

unread,
Sep 29, 2020, 5:17:33 PM9/29/20
to
This would technically be assignment, not initialization, with the known
drawbacks of "delayed init" (having uninitialized data around for a
while or need to invent dummy placeholder values; also, cannot have
const members).

olcott

unread,
Sep 29, 2020, 5:38:40 PM9/29/20
to
>>>> in the constructor.
>>>
>>> Sorry, but no. The standard says in [class.base.init]: "Then,
>>> non-static data members are initialized in the order they were
>>> declared in the class definition

I proved that the constructor can force the initialization order to be
different than the order they were declared in the class definition.

David Brown

unread,
Sep 29, 2020, 5:54:05 PM9/29/20
to
I believe you misunderstand me. /Logically/ the constructor body can
specify a different order than the class definition (though the body
provides assignments, not initialisations - there is a difference). But
if the statements in the body are simple assignments like the ones
above, or even some function calls, then the compiler can re-arrange
them because there is no (defined) way for the program to see a difference.

olcott

unread,
Sep 29, 2020, 8:12:47 PM9/29/20
to
Okay great. How it an initialization different than an assignment under
the covers in the machine code?

David Brown

unread,
Sep 30, 2020, 2:26:26 AM9/30/20
to
I hope I don't need to explain to you the difference in C++ between a
constructor and an assignment operator? It can certainly be the case
for simple types that the generated assembly code is the same for both
operations - I'd expect that in this case.

Ralf Goertz

unread,
Sep 30, 2020, 4:29:49 AM9/30/20
to
Am Tue, 29 Sep 2020 23:53:44 +0200
schrieb David Brown <david...@hesbynett.no>:

> I believe you misunderstand me. /Logically/ the constructor body can
> specify a different order than the class definition (though the body
> provides assignments, not initialisations - there is a difference).
> But if the statements in the body are simple assignments like the ones
> above, or even some function calls, then the compiler can re-arrange
> them because there is no (defined) way for the program to see a
> difference.

Why is it then that in

struct X {
int i;
float z;
X(float z_, int i_) : z(z_), i(i_) {}
};

int main() {
X x(3.2,4);
}

with g++ -Wall I get:
ini.cc: In constructor ‘X::X(float, int)’:
ini.cc:3:11: warning: ‘X::z’ will be initialized after [-Wreorder]
3 | float z;
| ^
ini.cc:2:9: warning: ‘int X::i’ [-Wreorder]
2 | int i;
| ^
ini.cc:4:5: warning: when initialized here [-Wreorder]
4 | X(float z_, int i_) : z(z_), i(i_) {}
| ^

What is the point of that warning? If the compiler can (in this case
actually) initialize in any order why can't (or shouldn't) I write it in
any order? Even if the initialization of one member "foo" requires the
other member "bar" to be initialized first the compiler can figure out
the necessary order. And foo itself can't know anything about the the
position of bar in the struct so that it would matter, right?

David Brown

unread,
Sep 30, 2020, 6:04:41 AM9/30/20
to
You have to understand the difference between the semantics of the
language, and the code generated by the compiler.

The language rules say that for member initialisation like this, the
order follows the order of declaration in the class, not the order in
the initialisation clause. gcc helpfully warns you if the
initialisation clause does not match, because there you have code that
looks different from its actual meaning.

Within a constructor body, the order of assignment (not initialisation)
follows the order of the statements.


When generating object code for all this, a compiler can re-arrange
things in all sorts of ways to get more efficient object code, as long
as the result is identical in terms of the observable behaviour for the
program. Since C++ code cannot access either "i" or "z" within an X
object before the creation is finished, it doesn't matter which one is
/actually/ initialised first in the object code. In your example, since
"x" is not used, the compiler won't bother creating the object at all.
If you instead have:

X foo() {
X x(3.2,4);
return x;
}

the compiler (in my tests) will generate a single 64-bit load
instruction - it initialises both fields at the same time.

But if the fields had been more complex types, with their own
non-trivial constructors, the order /could/ matter and the compiler
would generate code that ordered initialisation according to the rules
of the language.

Paavo Helde

unread,
Sep 30, 2020, 9:05:00 AM9/30/20
to
I have seen this warning many times after code refactoring and it's one
of the most annoying ones. The compiler is trying to be too smart, or
not smart enough. It sees that you have written the mem-initializers in
the wrong order, and assumes you are stupid and you think this is the
order in which they are initialized. Thus it "helpfully" informs you
that this is not so.

If the compiler were a bit smarter, it would see these initializations
do not depend on each other and therefore it does not matter at all in
which order they get initialized, so there is no need for a warning.

Alternatively, if the compiler trusted the user to know the language
rules, there would be no need for a warning if the user writes the
initializers in a random order, as the user obviously trusts the
compiler for putting them into correct order automatically.



> If the compiler can (in this case
> actually) initialize in any order why can't (or shouldn't) I write it in
> any order? Even if the initialization of one member "foo" requires the
> other member "bar" to be initialized first the compiler can figure out
> the necessary order.

The compiler is not allowed to figure out the "correct" order, it has to
follow the declaration order.

I guess it's not clear for everybody why language rules are written the
way they are. The reason is simple, the members need to be destructed in
the opposite order of creation, regardless of by which constructor or in
which TU the object was constructed, and the declaration order is the
only order the destructor is guaranteed to know about.

And yes, sometimes the member initialization order is important. For
example, if a class contains a std::thread as a member, working with the
same object, then the thread member must be the last one declared,
otherwise the thread may start running before the main object is fully
constructed. Good luck for having the compiler to try to figure out such
things automatically!

Ralf Goertz

unread,
Sep 30, 2020, 9:15:34 AM9/30/20
to
Am Wed, 30 Sep 2020 12:04:19 +0200
Okay, but in the declaration you declare and nothing else. Why does it
matter in the initialzation phase that you had a different order when
you declared the class. Of course, if the initialization of (in my
example) z depended on i like in

X(float z_, int i_) : z(z_+i), i(i_) {}

then the order of initialization matters. So in this case gcc could warn
about the usage of uninitialized members or so. But I still couldn't
care less, whether z was *declared* before i or vice versa.

> But if the fields had been more complex types, with their own
> non-trivial constructors, the order /could/ matter and the compiler
> would generate code that ordered initialisation according to the rules
> of the language.

Can you give an example for that that would not be covered by the usage
of uninitialzed members? And even if you can why isn't then the order of
initialization the one I should care about. What I mean is why should I
or the compiler care that the declaration order is different. Is that
important in any way other than that it differs and the language says it
shouldn't?

James Kuyper

unread,
Sep 30, 2020, 9:31:48 AM9/30/20
to
Correct - which means that the issue only comes up when the order in
which the initializations occur does matter. If that is the case, you
can guarantee that the observable results are the same "as if" the
initializations had occurred in the required order, by specifying that
order in the constructor, even if that's not the same as the declaration
order (modulo access controls).

Ralf Goertz

unread,
Sep 30, 2020, 9:42:11 AM9/30/20
to
Am Wed, 30 Sep 2020 16:04:39 +0300
schrieb Paavo Helde <myfir...@osa.pri.ee>:

> And yes, sometimes the member initialization order is important. For
> example, if a class contains a std::thread as a member, working with
> the same object, then the thread member must be the last one declared,
> otherwise the thread may start running before the main object is fully
> constructed. Good luck for having the compiler to try to figure out
> such things automatically!

But that does only mean that I need to initialize in the correct order.
But the declaration order only matters because the language standards
say so, right?

Manfred

unread,
Sep 30, 2020, 11:23:34 AM9/30/20
to
The initialization order is important. And the standard tells you that
in order to control the initialization order you have to arrange the
/declaration/ order accordingly.

I would also like to stress that the initialization order is important
to be univocally defined for self-consistency of the object itself, i.e.
for the object to work.
Paavo has already mentioned the destructor requirement of reverse
destruction order (which is both a consistency requirement and a
performance enabler).
More generally member objects may interact with each other, so a
well-defined unique order of initialization is required for this to work.
Since there can be multiple constructors, the natural choice is the
order of declaration, which is guaranteed to be univocal across all
translation units.

Paavo Helde

unread,
Sep 30, 2020, 12:53:29 PM9/30/20
to
The requirement that members are deleted in reverse order of creation is
a hard one, this is related to RAII and without that guarantee it would
not be possible to build robust solutions if there are dependencies
between members (and if there are no such dependencies, then the order
of construction is not relevant at all, so no need to worry about that).

Thus, knowing the order of construction is needed for the destructor at
least. Theoretically it might be possible to record the order
dynamically in the constructor and store it inside the object, but this
would make the object larger and the destructor code more complicated.
Also, this would imply that different constructors may construct the
members in different order, making their dependencies and the life of
the programmer much more complex.

In short, it would introduce some scripting-language-like dynamic
indeterminism into the C++ fundamental data structures, for very little
benefit. I believe nobody wants to go that way. If you really want to
create member objects dynamically in arbitrary order, use e.g.
std::map<std::string, std::any>, it's not that it is not possible to do
such things in C++.

olcott

unread,
Sep 30, 2020, 1:24:49 PM9/30/20
to
Someone here seemed to indicate that Initialization may require fewer
assembly language steps than construction.

David Brown

unread,
Sep 30, 2020, 3:58:45 PM9/30/20
to
Initialisation always involves construction, so that statement does not
make sense.

For simple types with trivial construction and assignment, such as those
in examples in this thread, initialisation and assignment will likely
give the same generated code.

David Brown

unread,
Sep 30, 2020, 4:03:13 PM9/30/20
to
Why does initialisation order follow declaration order? Because that's
how the language is defined. I don't know /why/ it is defined that way,
but presumably it makes sense in some way.

Or do you mean, why does gcc (with the right flags) warn you when your
initialisers don't follow declaration order? That's simpler - as I
said, it is warning you because your code /appears/ to mean an order
that is different from the /actual/ order.

> Of course, if the initialization of (in my
> example) z depended on i like in
>
> X(float z_, int i_) : z(z_+i), i(i_) {}
>
> then the order of initialization matters. So in this case gcc could warn
> about the usage of uninitialized members or so. But I still couldn't
> care less, whether z was *declared* before i or vice versa.
>
>> But if the fields had been more complex types, with their own
>> non-trivial constructors, the order /could/ matter and the compiler
>> would generate code that ordered initialisation according to the rules
>> of the language.
>
> Can you give an example for that that would not be covered by the usage
> of uninitialzed members?

Make some classes whose constructors print out a message. Then make a
class containing members of those classes. The order of initialisation
of the members of the big class are then important to the output of the
program.

James Kuyper

unread,
Sep 30, 2020, 5:01:50 PM9/30/20
to
On 9/30/20 4:02 PM, David Brown wrote:
> On 30/09/2020 15:15, Ralf Goertz wrote:
>> Am Wed, 30 Sep 2020 12:04:19 +0200
...
>> Okay, but in the declaration you declare and nothing else. Why does it
>> matter in the initialzation phase that you had a different order when
>> you declared the class.
>
> Why does initialisation order follow declaration order? Because that's
> how the language is defined. I don't know /why/ it is defined that way,
> but presumably it makes sense in some way.

Leaving the order unspecified would cause problems in those cases where
the order matters. Having initialization order follow declaration order
is pretty much the simplest rule that could be defined, and puts the
order fully under the control of the class definition. I doubt that
there's any more complicated reasons than that for this decision.

...
>> Can you give an example for that that would not be covered by the usage
>> of uninitialzed members?
>
> Make some classes whose constructors print out a message. Then make a
> class containing members of those classes. The order of initialisation
> of the members of the big class are then important to the output of the
> program.

Ralf: keep in mind that Dave's suggestion of printing merely serves as
an example. Any other shared resource would serve equally well as a
reason why the order would matter.

olcott

unread,
Sep 30, 2020, 7:27:40 PM9/30/20
to
That is what I thought.

Ian Collins

unread,
Sep 30, 2020, 8:04:28 PM9/30/20
to
Only in the limited case of non-const and non-reference members.

--
Ian.


olcott

unread,
Sep 30, 2020, 8:20:40 PM9/30/20
to
I think that it has been agreed that members can be constructed in any
arbitrary order overriding the order that they were declared.

Ian Collins

unread,
Sep 30, 2020, 8:49:01 PM9/30/20
to
No, it hasn't.

Consider

struct X
{
int a;
const int b;

X( int _a, int _b ) : b {_b}, a {_a} {}
};

$ clang++ -Wall x.cc
x.cc:6:25: warning: field 'b' will be initialized after field 'a'
[-Wreorder-ctor]
X( int _a, int _b ) : b {_b}, a {_a} {}
^
1 warning generated.

'b' must be initialised in an initialiser list.

--
Ian.

olcott

unread,
Sep 30, 2020, 9:54:55 PM9/30/20
to
> struct X
> {
> int a;
> int b;
>
> X( int _a, int _b ){ b = _b; a = _a; };
> };

const have to be initialized they can't be constructed.

Ian Collins

unread,
Sep 30, 2020, 9:57:58 PM9/30/20
to
I think you are confusing initialisation, construction and assignment...

--
Ian.

olcott

unread,
Sep 30, 2020, 10:51:15 PM9/30/20
to
Yes.

David Brown

unread,
Oct 1, 2020, 2:28:45 AM10/1/20
to
On 30/09/2020 23:01, James Kuyper wrote:
> On 9/30/20 4:02 PM, David Brown wrote:
>> On 30/09/2020 15:15, Ralf Goertz wrote:
>>> Am Wed, 30 Sep 2020 12:04:19 +0200
> ...
>>> Okay, but in the declaration you declare and nothing else. Why does it
>>> matter in the initialzation phase that you had a different order when
>>> you declared the class.
>>
>> Why does initialisation order follow declaration order? Because that's
>> how the language is defined. I don't know /why/ it is defined that way,
>> but presumably it makes sense in some way.
>
> Leaving the order unspecified would cause problems in those cases where
> the order matters. Having initialization order follow declaration order
> is pretty much the simplest rule that could be defined, and puts the
> order fully under the control of the class definition. I doubt that
> there's any more complicated reasons than that for this decision.
>

Yes, that sounds reasonable. Someone else in this thread mentioned
destructor order, and of course that is another reason why
initialisation order must be consistent (in the semantics of the
language, ignoring the "as if" rule). This really should have been
obvious to me if I had thought before writing!

> ...
>>> Can you give an example for that that would not be covered by the usage
>>> of uninitialzed members?
>>
>> Make some classes whose constructors print out a message. Then make a
>> class containing members of those classes. The order of initialisation
>> of the members of the big class are then important to the output of the
>> program.
>
> Ralf: keep in mind that Dave's suggestion of printing merely serves as
> an example. Any other shared resource would serve equally well as a
> reason why the order would matter.
>

Indeed.

Stuart Redmann

unread,
Oct 1, 2020, 3:38:01 AM10/1/20
to
David Brown <david...@hesbynett.no> wrote:
> On 30/09/2020 23:01, James Kuyper wrote:
>> On 9/30/20 4:02 PM, David Brown wrote:
>>> On 30/09/2020 15:15, Ralf Goertz wrote:
>>>> Am Wed, 30 Sep 2020 12:04:19 +0200
>> ...
>>>> Okay, but in the declaration you declare and nothing else. Why does it
>>>> matter in the initialzation phase that you had a different order when
>>>> you declared the class.
>>>
>>> Why does initialisation order follow declaration order? Because that's
>>> how the language is defined. I don't know /why/ it is defined that way,
>>> but presumably it makes sense in some way.
>>
>> Leaving the order unspecified would cause problems in those cases where
>> the order matters. Having initialization order follow declaration order
>> is pretty much the simplest rule that could be defined, and puts the
>> order fully under the control of the class definition. I doubt that
>> there's any more complicated reasons than that for this decision.
>>
>
> Yes, that sounds reasonable. Someone else in this thread mentioned
> destructor order, and of course that is another reason why
> initialisation order must be consistent (in the semantics of the
> language, ignoring the "as if" rule). This really should have been
> obvious to me if I had thought before writing!

Don‘t worry, I‘ve been following this thread because I knew there was a
simple and compelling reason, why the initialization order had to be tied
to the declaration order, but I just couldn’t remember it. Hopefully it
will not slip my mind so easily again :-)

Of course, the idea that the order of the initializations in the
initialization list could determine the initialization order is also
attractive. If a class has only one constructor, it could be done without
any problems (some rules would be necessary with regard to members that are
initialized at the point of their declaration). However, if a class
provided multiple constructors, and the initialization list order of these
constructors differed, the class would have to remember the initialization
order somehow (simple enum should suffice), so that it can employ the
proper destruction order in the destructor. This would mean, that the
class‘ size had to increase.

Come to think about it, the scheme with the flexible initialization order
is not so bad as it gives us more flexibility. And if we don’t use this
flexibility, it does not add any penalties (more memory, more instructions)
to the current way of doing things, this neatly embracing the design
principle of C++: „Don’t pay for something that you don’t use.“

Regards,
Stuart

David Brown

unread,
Oct 1, 2020, 3:48:49 AM10/1/20
to
Yes, and that would quickly be a mess. Order by declaration in the
class is likely to be the simplest and most practical solution.

Ralf Goertz

unread,
Oct 1, 2020, 4:00:24 AM10/1/20
to
Am Wed, 30 Sep 2020 17:23:13 +0200
schrieb Manfred <non...@add.invalid>:

> On 9/30/2020 3:41 PM, Ralf Goertz wrote:
> > Am Wed, 30 Sep 2020 16:04:39 +0300
> > schrieb Paavo Helde <myfir...@osa.pri.ee>:
> >
> >> And yes, sometimes the member initialization order is important.
> >> For example, if a class contains a std::thread as a member,
> >> working with the same object, then the thread member must be the
> >> last one declared, otherwise the thread may start running before
> >> the main object is fully constructed. Good luck for having the
> >> compiler to try to figure out such things automatically!
> >
> > But that does only mean that I need to initialize in the correct
> > order. But the declaration order only matters because the language
> > standards say so, right?
> >
>
> The initialization order is important. And the standard tells you
> that in order to control the initialization order you have to arrange
> the /declaration/ order accordingly.

I don't want to sound obstreperous (I just looked up that word, never
heard it before but I hope it means what the dictionary suggests). I'm
perfectly aware that the initialization order is important. What I don't
see is why it has to follow declaration order. Why not not care at all
about declaration order and initialize in the order of the explicit
initialization? I don't see how this can hurt. If there are no explicit
initializations in the class there is no harm in doing the implicit
initialization in declaration order. But when explicit initialization is
used do it in that order. If that leads to the access of uninitialized
memory the compiler should warn about that and not about the wrong
order.

> I would also like to stress that the initialization order is
> important to be univocally defined for self-consistency of the object
> itself, i.e. for the object to work.
> Paavo has already mentioned the destructor requirement of reverse
> destruction order (which is both a consistency requirement and a
> performance enabler).

I also don't see how the destructor order comes into play. Just have the
compiler destroy the objects in the reverse order it initialized them
regardless of where the order came from.

> More generally member objects may interact with each other, so a
> well-defined unique order of initialization is required for this to
> work. Since there can be multiple constructors, the natural choice is
> the order of declaration, which is guaranteed to be univocal across
> all translation units.

But every code that initializes or destroys an object knows the complete
class and can take care of the correct order. A translation unit can't
destroy anything it hasn't constructed right?. I do see that for
consistence sake it is *nice* to have the same declaration and
initialization order. But I am not sure it is necessary.

Ralf Goertz

unread,
Oct 1, 2020, 4:04:48 AM10/1/20
to
Am Wed, 30 Sep 2020 19:53:04 +0300
Maybe my understanding is too limited here. But I thought that in order
to destroy an object in a particular translation unit this unit must be
able to see the whole class. So the initialization order should be
easily deductable.

Ralf Goertz

unread,
Oct 1, 2020, 4:11:59 AM10/1/20
to
Am Thu, 1 Oct 2020 09:37:38 +0200
schrieb Stuart Redmann <DerT...@web.de>:

> Of course, the idea that the order of the initializations in the
> initialization list could determine the initialization order is also
> attractive. If a class has only one constructor, it could be done
> without any problems (some rules would be necessary with regard to
> members that are initialized at the point of their declaration).
> However, if a class provided multiple constructors, and the
> initialization list order of these constructors differed, the class
> would have to remember the initialization order somehow (simple enum
> should suffice), so that it can employ the proper destruction order in
> the destructor. This would mean, that the class‘ size had to increase.

I admit that differing initialization orders in different constructors
would be a problem. But that would then be the right place to have the
compiler spit out an order warning.

> Come to think about it, the scheme with the flexible initialization
> order is not so bad as it gives us more flexibility. And if we don’t
> use this flexibility, it does not add any penalties (more memory,
> more instructions) to the current way of doing things, this neatly
> embracing the design principle of C++: „Don’t pay for something that
> you don’t use.“

Exactly, :-)

Paavo Helde

unread,
Oct 1, 2020, 4:39:25 AM10/1/20
to
01.10.2020 10:37 Stuart Redmann kirjutas:

> Of course, the idea that the order of the initializations in the
> initialization list could determine the initialization order is also
> attractive. If a class has only one constructor, it could be done without
> any problems (some rules would be necessary with regard to members that are
> initialized at the point of their declaration). However, if a class
> provided multiple constructors, and the initialization list order of these
> constructors differed, the class would have to remember the initialization
> order somehow (simple enum should suffice), so that it can employ the
> proper destruction order in the destructor. This would mean, that the
> class‘ size had to increase.
>
> Come to think about it, the scheme with the flexible initialization order
> is not so bad as it gives us more flexibility.

This is a kind of flexibility similar to replacing for loops with gotos,
or worse. A complicated and error-prone solution to a non-existing problem.

> And if we don’t use this
> flexibility, it does not add any penalties (more memory, more instructions)
> to the current way of doing things, this neatly embracing the design
> principle of C++: „Don’t pay for something that you don’t use.“

Increasing the class size means a penalty, and in general this cannot be
avoided because the class size must be determined by the class
definition in the header file, whereas constructor definitions might
only appear in separate translation units or libraries and might not be
accessible at this point. Moreover, changing the cpp file containing a
class constructor may now change the class size, requiring recompilation
of all code including the class header file, even though the header file
itself is not changed!

Paavo Helde

unread,
Oct 1, 2020, 4:48:00 AM10/1/20
to
01.10.2020 11:00 Ralf Goertz kirjutas:
>
> But every code that initializes or destroys an object knows the complete
> class and can take care of the correct order. A translation unit can't
> destroy anything it hasn't constructed right?.

Of course it can. I guess you are forgetting about dynamically allocated
objects.

Even if all class destructor and all constructors (including
compiler-generated ones) happen to be defined in the same translation
unit, the destructor might still not know by which constructor the
object was created, and the correct order would need to be encoded
somewhere, taking extra space and extra time for processing.

Fred. Zwarts

unread,
Oct 1, 2020, 5:54:02 AM10/1/20
to
Op 01.okt..2020 om 10:11 schreef Ralf Goertz:
> Am Thu, 1 Oct 2020 09:37:38 +0200
> schrieb Stuart Redmann <DerT...@web.de>:
>
>> Of course, the idea that the order of the initializations in the
>> initialization list could determine the initialization order is also
>> attractive. If a class has only one constructor, it could be done
>> without any problems (some rules would be necessary with regard to
>> members that are initialized at the point of their declaration).
>> However, if a class provided multiple constructors, and the
>> initialization list order of these constructors differed, the class
>> would have to remember the initialization order somehow (simple enum
>> should suffice), so that it can employ the proper destruction order in
>> the destructor. This would mean, that the class‘ size had to increase.
>
> I admit that differing initialization orders in different constructors
> would be a problem. But that would then be the right place to have the
> compiler spit out an order warning.

That could be almost impossible for a compiler, as the two constructors
might be in different compilation units. So, the compiler does not
necessarily see the different orders in one compilation run.


--
Paradoxes in the relation between Creator and creature.
<http://www.wirholt.nl/English>.

Fred. Zwarts

unread,
Oct 1, 2020, 6:00:54 AM10/1/20
to
Op 01.okt..2020 om 10:04 schreef Ralf Goertz:
No, that's why there are virtual destructors. A pointer to a base class
in a translation unit could point to a derived class that is not known
in that translation unit. When deleting the pointer the translation unit
just calls the virtual destructor of the derived class, without knowing
anything of the derived class. So, the information of the destruction
order must be present somewhere inside the class.

Manfred

unread,
Oct 1, 2020, 9:45:03 AM10/1/20
to
On 10/1/2020 10:00 AM, Ralf Goertz wrote:
> Am Wed, 30 Sep 2020 17:23:13 +0200
> schrieb Manfred <non...@add.invalid>:
>
>> On 9/30/2020 3:41 PM, Ralf Goertz wrote:
>>> Am Wed, 30 Sep 2020 16:04:39 +0300
>>> schrieb Paavo Helde <myfir...@osa.pri.ee>:
>>>
>>>> And yes, sometimes the member initialization order is important.
>>>> For example, if a class contains a std::thread as a member,
>>>> working with the same object, then the thread member must be the
>>>> last one declared, otherwise the thread may start running before
>>>> the main object is fully constructed. Good luck for having the
>>>> compiler to try to figure out such things automatically!
>>>
>>> But that does only mean that I need to initialize in the correct
>>> order. But the declaration order only matters because the language
>>> standards say so, right?
>>>
>>
>> The initialization order is important. And the standard tells you
>> that in order to control the initialization order you have to arrange
>> the /declaration/ order accordingly.
>
> I don't want to sound obstreperous
That's a nice one :)

(I just looked up that word, never
> heard it before but I hope it means what the dictionary suggests). I'm
> perfectly aware that the initialization order is important. What I don't
> see is why it has to follow declaration order. Why not not care at all
> about declaration order and initialize in the order of the explicit
> initialization? I don't see how this can hurt. If there are no explicit
> initializations in the class there is no harm in doing the implicit
> initialization in declaration order. But when explicit initialization is
> used do it in that order. If that leads to the access of uninitialized
> memory the compiler should warn about that and not about the wrong
> order.
See below.

>
>> I would also like to stress that the initialization order is
>> important to be univocally defined for self-consistency of the object
>> itself, i.e. for the object to work.
>> Paavo has already mentioned the destructor requirement of reverse
>> destruction order (which is both a consistency requirement and a
>> performance enabler).
>
> I also don't see how the destructor order comes into play. Just have the
> compiler destroy the objects in the reverse order it initialized them
> regardless of where the order came from.
That would be a runtime operation that may be impossible to define at
compile time.

>
>> More generally member objects may interact with each other, so a
>> well-defined unique order of initialization is required for this to
>> work. Since there can be multiple constructors, the natural choice is
>> the order of declaration, which is guaranteed to be univocal across
>> all translation units.
>
> But every code that initializes or destroys an object knows the complete
> class and can take care of the correct order.
That's the point: it knows the complete class /as declared/, because the
class definition is its declaration, not the (initialization list of
the) constructor.

A translation unit can't
> destroy anything it hasn't constructed right?. I do see that for
> consistence sake it is *nice* to have the same declaration and
> initialization order. But I am not sure it is necessary.
>
It is necessary because there can be multiple translation units that
define multiple constructors, plus a possibly additional translation
unit that defines the destructor. All of them must follow a unique
definition, that is the class /declaration/.

Tim Rentsch

unread,
Oct 3, 2020, 8:12:12 AM10/3/20
to
Jorgen Grahn <grahn...@snipabacken.se> writes:
> On Tue, 2020-09-29, Keith Thompson wrote:
>> Richard Damon <Ric...@Damon-Family.org> writes:
>>> On 9/28/20 2:31 PM, olcott wrote:
>>>
>>>> All that I had to do to optimize the need for padding of the
>>>> reference struct is simply sort the members in size order from
>>>> largest to smallest. Certainly every compiler could compare the
>>>> need for padding of the specified version with the sorted version
>>>> and then know whether or not sorting reduces padding
>>>> requirements.
>>>
>>> I believe the standard requires that the order of the members in
>>> the struct has to match the order they are declared in the struct,
>>> at least as long as a access specifier doesn't exist between them.
>>>
>>> That rule comes from C, without the exception since C doesn't
>>> have access specifiers. As you alluded to elsewhere, allowing
>>> rearangement can cause all sorts of issues with code that makes
>>> some otherwise fairly safe assumptions. I suspect that the rule
>>> came out because the early compilers [weren't] smart enough to
>>> rearrange, then a lot of code was built with that assumption,
>>> and it became effectively impossible to safely rearrange so it
>>> was defined that it couldn't.

I believe it was more deliberate than that. See below.

> If that's what happened, it's a really bad combination: letting
> bad code prevent important optimizations. I can see no reason for
> portable code to know the struct layout (except perhaps that you
> can cast between &foo and &foo.first_element).
>
> One could easily imagine each ABI defining struct layout, with
> rearrangements, in such a way that waste is minimized.

I think you're overlooking an important property, and one that
pertains to having code be portable. The rule in C (and later
also in C++) is not just that struct members are positioned in
the same order as they are declared, but also that their offsets
depend only on (the types of) the previously declared members.
This rule must be followed in order to (reasonably) support the
semantics related to /common initial sequences/, a term defined
in both the C and C++ standards. The notion that struct layouts
could be rearranged as suggested above is therefore not feasible.

Keith Thompson

unread,
Oct 3, 2020, 3:49:26 PM10/3/20
to
Tim Rentsch <tr.1...@z991.linuxsc.com> writes:
[...]
> I think you're overlooking an important property, and one that
> pertains to having code be portable. The rule in C (and later
> also in C++) is not just that struct members are positioned in
> the same order as they are declared, but also that their offsets
> depend only on (the types of) the previously declared members.
> This rule must be followed in order to (reasonably) support the
> semantics related to /common initial sequences/, a term defined
> in both the C and C++ standards. The notion that struct layouts
> could be rearranged as suggested above is therefore not feasible.

To be clear, this rule isn't (quite) stated explicitly, but it
follows from the common initial sequence rule in N1570 6.5.2.3.

In principle I suppose a compiler could violate the rule in cases
where it knows that there's no union containing a given structure
as a member, but I can't think of any good reason to do so.

(I'm wondering at what point I should start citing a newer draft.)

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for Philips Healthcare
void Void(void) { Void(); } /* The recursive call of the void */

Tim Rentsch

unread,
Nov 9, 2020, 2:03:46 AM11/9/20
to
Keith Thompson <Keith.S.T...@gmail.com> writes:

> Tim Rentsch <tr.1...@z991.linuxsc.com> writes:
> [...]
>
>> I think you're overlooking an important property, and one that
>> pertains to having code be portable. The rule in C (and later
>> also in C++) is not just that struct members are positioned in
>> the same order as they are declared, but also that their offsets
>> depend only on (the types of) the previously declared members.
>> This rule must be followed in order to (reasonably) support the
>> semantics related to /common initial sequences/, a term defined
>> in both the C and C++ standards. The notion that struct layouts
>> could be rearranged as suggested above is therefore not feasible.
>
> To be clear, this rule isn't (quite) stated explicitly, but it
> follows from the common initial sequence rule in N1570 6.5.2.3.

Right. There is no explicit rule, it is only a consequence of
other stated requirements.

> In principle I suppose a compiler could violate the rule in cases
> where it knows that there's no union containing a given structure
> as a member,

I think so, but it isn't easy to decide which sets of circumstances
would allow it. There may not be a union in this TU, but I think
there are cases where there is one in another TU that would prevent
an alternate choice from being conforming. It's a messy problem.

> but I can't think of any good reason to do so.

Me either. In practical terms I believe there is no viable
alternative.
0 new messages