Jump to page: 1 2
Thread overview
Packing of Struct Fields
Oct 16, 2020
Per Nordlöw
Oct 16, 2020
ag0aep6g
Oct 17, 2020
Per Nordlöw
Oct 17, 2020
Per Nordlöw
Oct 17, 2020
Adam D. Ruppe
Oct 17, 2020
Per Nordlöw
Oct 17, 2020
ag0aep6g
Oct 17, 2020
Per Nordlöw
Oct 17, 2020
Adam D. Ruppe
Oct 17, 2020
Per Nordlöw
Oct 17, 2020
Per Nordlöw
Oct 16, 2020
Ali Çehreli
October 16, 2020
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;
}

?
October 16, 2020
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.
October 16, 2020
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
   5: ... 3-byte PADDING
=== Memory layout of 'T' (.sizeof: 12, .alignof: 4) ===
   0: S s
   8: char c
   9: ... 3-byte PADDING
=== Memory layout of 'U' (.sizeof: 8, .alignof: 4) ===
   0: int i
   4: bool b
   5: char c
   6: ... 2-byte PADDING

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);
    }

    /* Prints padding information if padding is actually
     * observed. */
    void maybePrintPaddingInfo(size_t expectedOffset,
                               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,
                      format("... %s-byte PADDING",
                             paddingSize));
        }
    }

    /* This is the expected offset of the next member if there
     * were no padding bytes before that member. */
    size_t noPaddingOffset = 0;

    /* 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;
        maybePrintPaddingInfo(noPaddingOffset, offset);

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

        noPaddingOffset = offset + member.sizeof;
    }

    maybePrintPaddingInfo(noPaddingOffset, T.sizeof);
}

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

Ali

October 16, 2020
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
October 17, 2020
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?
}

October 17, 2020
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...
October 17, 2020
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.
October 17, 2020
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.
October 17, 2020
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.
October 17, 2020
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
   9: ... 3-byte PADDING
« First   ‹ Prev
1 2