Search
Packing of Struct Fields
3 days ago
Per Nordlöw
3 days ago
ag0aep6g
3 days ago
Per Nordlöw
3 days ago
Per Nordlöw
3 days ago
3 days ago
Per Nordlöw
3 days ago
ag0aep6g
3 days ago
Per Nordlöw
3 days ago
3 days ago
Per Nordlöw
3 days ago
Per Nordlöw
3 days ago
Ali Çehreli
```Why is `T.sizeof` 12 instead of 8 when `U.sizeof` is 8 in the following example?

struct S
{
int i;
bool b;
}

struct T
{
S s;
char c;
}

struct U
{
int i;
bool b;
char c;
}

?
```
```On 16.10.20 22:32, Per Nordlöw wrote:
> Why is `T.sizeof` 12 instead of 8 when `U.sizeof` is 8 in the following example?
>
> struct S
> {
>      int i;
>      bool b;
> }
>
> struct T
> {
>      S s;
>      char c;
> }
>
> struct U
> {
>      int i;
>      bool b;
>      char c;
> }
>
> ?

S.sizeof: 4 bytes for the int + 1 byte for the bool + 3 bytes padding so that the int is aligned = 8 bytes.

T.sizeof: 8 bytes for the S + 1 byte for the char + 3 bytes padding so that the S is aligned = 12 bytes.

U.sizeof: 4 bytes for the int + 1 byte for the bool + 1 byte for the char + 2 bytes padding so that the int is aligned = 8 bytes.
```
```On 10/16/20 1:32 PM, Per Nordlöw wrote:
> Why is `T.sizeof` 12 instead of 8 when `U.sizeof` is 8 in the following example?
>
> struct S
> {
>      int i;
>      bool b;
> }
>
> struct T
> {
>      S s;
>      char c;
> }
>
> struct U
> {
>      int i;
>      bool b;
>      char c;
> }
>
> ?

I have a function that dumps member layout of structs, which someone may find useful:

http://ddili.org/ders/d.en/memory.html#ix_memory..offsetof

It prints the following for these types:

=== Memory layout of 'S' (.sizeof: 8, .alignof: 4) ===
0: int i
4: bool b
=== Memory layout of 'T' (.sizeof: 12, .alignof: 4) ===
0: S s
8: char c
=== Memory layout of 'U' (.sizeof: 8, .alignof: 4) ===
0: int i
4: bool b
5: char c

Copied here:

struct S
{
int i;
bool b;
}

struct T
{
S s;
char c;
}

struct U
{
int i;
bool b;
char c;
}

void printObjectLayout(T)()
if (is (T == struct) || is (T == union)) {
import std.stdio;
import std.string;

writefln("=== Memory layout of '%s'" ~
" (.sizeof: %s, .alignof: %s) ===",
T.stringof, T.sizeof, T.alignof);

/* Prints a single line of layout information. */
void printLine(size_t offset, string info) {
writefln("%4s: %s", offset, info);
}

* observed. */
size_t actualOffset) {
if (expectedOffset < actualOffset) {
/* There is some padding because the actual offset
* is beyond the expected one. */

const paddingSize = actualOffset - expectedOffset;

printLine(expectedOffset,
}
}

/* This is the expected offset of the next member if there
* were no padding bytes before that member. */

/* Note: __traits(allMembers) is a 'string' collection of
* names of the members of a type. */
foreach (memberName; __traits(allMembers, T)) {
mixin (format("alias member = %s.%s;",
T.stringof, memberName));

const offset = member.offsetof;

const typeName = typeof(member).stringof;
printLine(offset,
format("%s %s", typeName, memberName));

}

}

void main() {
printObjectLayout!S();
printObjectLayout!T();
printObjectLayout!U();
}

Ali

```
```On 10/16/20 4:44 PM, ag0aep6g wrote:
> On 16.10.20 22:32, Per Nordlöw wrote:
>> Why is `T.sizeof` 12 instead of 8 when `U.sizeof` is 8 in the following example?
>>
>> struct S
>> {
>>      int i;
>>      bool b;
>> }
>>
>> struct T
>> {
>>      S s;
>>      char c;
>> }
>>
>> struct U
>> {
>>      int i;
>>      bool b;
>>      char c;
>> }
>>
>> ?
>
> S.sizeof: 4 bytes for the int + 1 byte for the bool + 3 bytes padding so that the int is aligned = 8 bytes.
>
> T.sizeof: 8 bytes for the S + 1 byte for the char + 3 bytes padding so that the S is aligned = 12 bytes.
>
> U.sizeof: 4 bytes for the int + 1 byte for the bool + 1 byte for the char + 2 bytes padding so that the int is aligned = 8 bytes.

To further explain this -- the padding is added so things like pointer arithmetic on an array work.

For example, if you have a T* t, and you say t += 1, you want it to go to the next T, not to a misaligned spot.

You can also override this with align keyword. But I don't recommended this unless you know what you are doing. Misaligned reads/writes are different on different architectures, but even if they work and don't crash your program, they are going to be slower.

https://dlang.org/spec/attribute.html#align

-Steve
```
```On Friday, 16 October 2020 at 21:26:12 UTC, Steven Schveighoffer wrote:
> To further explain this -- the padding is added so things like pointer arithmetic on an array work.

In my code sample above one can only access the first element anyhow so I don't understand why this restriction is imposed here.

struct S
{
int i;
bool b;
}

struct T
{
S s; // reinterpreting this as an array can only access this first element anyway
char c; // so why can't this be aligned directly after `s` without any padding?
}

```
```On Saturday, 17 October 2020 at 12:35:37 UTC, Per Nordlöw wrote:
> On Friday, 16 October 2020 at 21:26:12 UTC, Steven Schveighoffer wrote:
>> To further explain this -- the padding is added so things like pointer arithmetic on an array work.
>
> In my code sample above one can only access the first element anyhow so I don't understand why this restriction is imposed here.
>
> struct S
> {
>     int i;
>     bool b;
> }
>
> struct T
> {
>     S s; // reinterpreting this as an array can only access this first element anyway
>     char c; // so why can't this be aligned directly after `s` without any padding?
> }

So AFAICT the key question becomes:

Can `align`s be inserted in S or/and T so that T is packed to 8 bytes but still aligned to 8 bytes? I don't see why this shouldn't be the default behaviour...
```
```On Saturday, 17 October 2020 at 12:44:44 UTC, Per Nordlöw wrote:
> Can `align`s be inserted in S or/and T so that T is packed to 8 bytes but still aligned to 8 bytes?

Yes. Put an align on the OUTSIDE of the struct you are nesting, then put one INSIDE the struct you want the contents packed.

> I don't see why this shouldn't be the default behaviour...

It is generally slower.
```
```On Saturday, 17 October 2020 at 12:44:44 UTC, Per Nordlöw wrote:
> Can `align`s be inserted in S or/and T so that T is packed to 8 bytes but still aligned to 8 bytes? I don't see why this shouldn't be the default behaviour...

I though this would do the trick but not...

struct S
{
int i;                  // 4 bytes
short s;                // 2 byte
bool b;                 // 1 byte
}
static assert(S.sizeof == 8);
static assert(S.alignof == 4);
align(4) struct T
{
align(4) S s;
align(1) char c;
}
static assert(T.alignof == 4);
// TODO: static assert(T.sizeof == 8);

T.sizeof is still 12 bytes, I want it to be 8.
```
```On 17.10.20 14:35, Per Nordlöw wrote:
> struct S
> {
>      int i;
>      bool b;
> }
>
> struct T
> {
>      S s; // reinterpreting this as an array can only access this first element anyway
>      char c; // so why can't this be aligned directly after `s` without any padding?
> }
>

c does come directly after s. The padding between b and c is part of s. If you don't want that padding, you can use `align(1)` to define S without padding. But then 75% of the ints in an S[] will be misaligned.
```
```On Saturday, 17 October 2020 at 12:51:21 UTC, ag0aep6g wrote:
> c does come directly after s. The padding between b and c is part of s. If you don't want that padding, you can use `align(1)` to define S without padding. But then 75% of the ints in an S[] will be misaligned.

I understand that. I don't want the alignment of `S` to change. I want the padding after `s` in `T` to be avoided and have `c` start at byte-offset 7. I don't see why this padding is needed in the case where only a single (1-element array of) `S` is stored as a field inside another aggregate.

Ali's code prints:

=== Memory layout of 'T' (.sizeof: 12, .alignof: 4) ===
0: S s
8: char c