7

Preface: Did my research about struct alignment. Looked at this question, this one and also this one - but still did not find my answer.

My Actual Question:

Here is a code snippet I created in order to clarify my question:

#include "stdafx.h"
#include <stdio.h>

struct IntAndCharStruct
{
    int a;
    char b;
};

struct IntAndDoubleStruct
{
    int a;
    double d;
};

struct IntFloatAndDoubleStruct
{
    int a;
    float c;
    double d;
};

int main()
{
    printf("Int: %d\n", sizeof(int));
    printf("Float: %d\n", sizeof(float));
    printf("Char: %d\n", sizeof(char));
    printf("Double: %d\n", sizeof(double));
    printf("IntAndCharStruct: %d\n", sizeof(IntAndCharStruct));
    printf("IntAndDoubleStruct: %d\n", sizeof(IntAndDoubleStruct));
    printf("IntFloatAndDoubleStruct: %d\n", sizeof(IntFloatAndDoubleStruct));
    getchar();
}

And it's output is:

Int: 4
Float: 4
Char: 1
Double: 8
IntAndCharStruct: 8
IntAndDoubleStruct: 16
IntFloatAndDoubleStruct: 16

I get the alignment seen in the IntAndCharStruct and in the IntAndDoubleStruct.

But I just don't get the IntFloatAndDoubleStruct one.

Simply put: Why isn't sizeof(IntFloatAndDoubleStruct) = 24?

Thanks in advance!

p.s: I'm using Visual-Studio 2017, standard console application.

Edit: Per comments, tested IntDoubleAndFloatStruct (different order of elements) and got 24 in the sizeof() - And I will be happy if answers will note and explain this case too.

MordechayS
  • 1,552
  • 2
  • 19
  • 29
  • 2
    Switch the order of float and double and you should get 24. – Gerhardh Jun 12 '18 at 10:58
  • 1
    Why would you expect to get 24? What do you expect for `int` and `float` without `double` and why? – Gerhardh Jun 12 '18 at 11:00
  • 1
    @Gerhardh - switched the order, and did get a 24! As for my expectations, I was expecting an alignment to the biggest element - so 8 bytes * 3 elements = 24. Int and Float have the same size (4), so 8 is OK IMO. – MordechayS Jun 12 '18 at 11:05
  • You must disctinct alignment of whole structure from alignment of each member. – Gerhardh Jun 12 '18 at 11:11
  • @Gerhardh Can you explain more about this? – MordechayS Jun 12 '18 at 11:16
  • The answer from ClsForcookies addresses this topic. Struct alignment is based on largest member: 8 bytes. But within the struct, each member only needs alignment for its own type => 4 bytes for float. – Gerhardh Jun 12 '18 at 11:18
  • FYI, an algorithm by which compilers can lay out a structure and determine its size is [here](https://stackoverflow.com/a/14510919/298225). – Eric Postpischil Jun 12 '18 at 11:27
  • @Gerhardh: Structure alignment is based on the strictest alignment of its members, not on its largest member. E.g., a structure might have an eight-byte `double` that requires four-byte alignment and a 20-byte array of `char` that requires only one-byte alignment, and the structure would be required to be aligned to a multiple of four bytes, not one or 20. (Or, if we wished to consider theoretical exotic architectures, the alignment requirement of the structure would be the least common multiple of the alignment requirements of its members.) – Eric Postpischil Jun 12 '18 at 11:28
  • 1
    I like your list of links you studied, instead of just writing "I read everything." as so many other questions do. Good work. – Yunnosch Jun 12 '18 at 11:37
  • 1
    [The correct format specifier for `sizeof()` is `"%zu"`.](https://stackoverflow.com/questions/27296011/correct-format-specifier-for-return-value-of-sizeof-in-c) – Andrew Henle Jun 12 '18 at 12:34
  • @MordechayS when you consider having got the good answer you should accept it – alinsoar Jun 13 '18 at 07:13
  • @alinsoar Still trying to find appropriate time to read all of the great comments and answers, and then choose. Thanks! – MordechayS Jun 13 '18 at 07:16

3 Answers3

6

On your platform, the following holds: The size of int and float are both 4. The size & alignment requirement of double is 8.

We know this from the sizeof output you've shown. sizeof (T) gives the number of bytes between the addresses of two consecutive elements of type T in an array. So we know that the alignment requirements are as I've said above. (Note)

Now, the compiler reported 16 for IntFloatAndDoubleStruct. Does it work out?

Assume we have such an object at an address aligned to 16.

  • int a is therefore at address X aligned to 16, so it's aligned to 4 just fine. It will occupy bytes [X, X+4)
  • This means float c could start at X+4, which is aligned to 4, which is fine for float. It will occupy bytes [X+4, X+8)
  • Finally, double d could start at X+8, which is aligned to 8, which is fine for double. It will occupy bytes [X+8, X+16)
  • This leaves X+16 free for the next struct object, again aligned to 16.

So there's no reason to start any of the members later, so the whole struct fits into 16 bytes just fine.


(Note) This is not strictly true: for each of these, we know that both size and alignment are <= N, that N is a multiple of the alignment requirement, and that there is no N1 < N for which this would also hold. However, this is a very fine detail, and for clarity the answer simply assumes the actual size and alignment requirements for the primitive types are indetical, which is the most likely case on the OP's platform anyway.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • But isn't the compiler aligning **all** elements to the same size (8)? I thought the 'int' should be aligned to 8 aswell! – MordechayS Jun 12 '18 at 11:09
  • @MordechayS Why should that be? `sizeof` effectively tells you the alignment requirement. I've expanded the answer. – Angew is no longer proud of SO Jun 12 '18 at 11:22
  • @Angew `sizeof` tell the alignement requirement for primitive types only – Tyker Jun 12 '18 at 11:23
  • @Angew: `sizeof` does not tell you the alignment requirement. A processor could have eight-byte `double` objects but only require them to be four-byte aligned. (This could happen because the processor has only four-byte accesses to memory, so it needs to use two accesses to load/store a double regardless of whether its address is 0 modulo 8 or 4 modulo 8, so it does not care which of those is true.) The alignment requirement of an object is reported by the `_Alignof` operator. – Eric Postpischil Jun 12 '18 at 11:25
  • @EricPostpischil Fair enough, that's why I qualified the comment with "effectively." It's debatable whether the question's apparent level warranties such fine precision, but I added a footnote to the answer. – Angew is no longer proud of SO Jun 12 '18 at 11:40
5

Your struct must be 8*N bytes long, since it has a member with 8 bytes (double). That means the struct sits in the memory at an address (A) divisible by 8 (A%8 == 0), and its end address will be (A + 8N) which will also be divisible by 8.

From there, you store 2 4-bytes variables (int + float) meaning you now occupy the memory area [A,A+8). Now you store an 8-byte variable (double). There is no need for padding since (A+8) % 8 == 0 [since A%8 == 0]. So, with no padding you get the 4+4+8 == 16.

If you change the order to int -> double -> float you'll occupy 24 bytes since the double variable original address will not be divisible by 8 and it will have to pad 4 bytes to get to a valid address (and also the struct will have padding at the end).


|--------||--------||--------||--------||--------||--------||--------||--------|
|   each ||   cell ||  here  ||represen||-ts  4  || bytes  ||        ||        |
|--------||--------||--------||--------||--------||--------||--------||--------|

A        A+4       A+8      A+12      A+16      A+20      A+24                      [addresses]
|--------||--------||--------||--------||--------||--------||--------||--------|    
|   int  ||  float || double || double ||        ||        ||        ||        |    [content - basic case]
|--------||--------||--------||--------||--------||--------||--------||--------|

first padding to ensure the double sits on address that is divisble by 8
last  padding to ensure the struct size is divisble by the largest member's size (8)
|--------||--------||--------||--------||--------||--------||--------||--------|    
|   int  || padding|| double || double || float  || padding||        ||        |    [content - change order case]
|--------||--------||--------||--------||--------||--------||--------||--------|
CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
  • First of all, just finished reading your answer - learned alot, thanks! But, I still have a question: in the "basic case" - why don't we need the float element to start from an address divisable by the struct's biggest element (8)? – MordechayS Jun 13 '18 at 07:54
  • primitives need to aligned to their size while structs need to be aligned to their biggest member's size (instead of their own size, i.e. in the basic case - alignment to 8 instead of 16) – CIsForCookies Jun 13 '18 at 08:18
  • Can you explain more about struct vs elements alignment? – MordechayS Jun 13 '18 at 08:40
  • Let me clarify: If alignment is used for performence/simplicity - why do we allow to put an element in a non-8-factor position? (like the float element in the basic case) – MordechayS Jun 13 '18 at 08:43
  • 1) performance/simplicity has a trade-off with memory usage. We can allow only 8-byte alignment for floats for simplicity but that would cost more. 2) As for the why - not sure. Can speculate that this was considered the correct way to pack a struck in memory - you slice it to chunks for your biggest type (the 8-bytes), and each chunk you fill the best you can (2 4-bytes / 1 4-byte + 4-padding and so on...) – CIsForCookies Jun 13 '18 at 09:22
  • You might want to edit your comments to your question, hopefully someone else will have a better answer – CIsForCookies Jun 13 '18 at 09:24
  • *coudln't add this to the previous comment...* 3) strcut vs. element - don't see a difference. Both are aligned externally by their size. Internally, the struct tries to keep up alignment with as little padding as possible, as usual – CIsForCookies Jun 13 '18 at 09:30
0

Compiler will insert padding in order to guarantee that each element is at offset that is some multiple of its size.

In this case int will be at offset=0 (relative to address of a structure instance), float at offset=4, and double at offset=8, because sizes of int and float add up to 8.

There's no padding at the end - size of the structure is already 16, which is a multiple of size of double.

joe_chip
  • 2,468
  • 1
  • 12
  • 23
  • Processors do not always require that an object be at an address that is a multiple of its size. It seems to hold in the specific case OP asked about, but a processor could have eight-byte `double` objects that only need four-byte alignment or could have ten-byte objects that require 16-byte alignment. – Eric Postpischil Jun 12 '18 at 11:20
  • @EricPostpischil: Sure. There's another possibility, too: no alignment is required, but aligned accesses are preferred. For example, assuming that OP is targeting x86/64, `MOVSD` used for loading `double` would be more expensive when address is unaligned - but still possible. – joe_chip Jun 12 '18 at 16:54