Jump to page: 1 2
Thread overview
October 16
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
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
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
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
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
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
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
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
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
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