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.

12
  • 2
    Switch the order of float and double and you should get 24. Commented Jun 12, 2018 at 10:58
  • 1
    Why would you expect to get 24? What do you expect for int and float without double and why? Commented Jun 12, 2018 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. Commented Jun 12, 2018 at 11:05
  • 1
    I like your list of links you studied, instead of just writing "I read everything." as so many other questions do. Good work. Commented Jun 12, 2018 at 11:37
  • 1
    The correct format specifier for sizeof() is "%zu". Commented Jun 12, 2018 at 12:34

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

5 Comments

But isn't the compiler aligning all elements to the same size (8)? I thought the 'int' should be aligned to 8 aswell!
@MordechayS Why should that be? sizeof effectively tells you the alignment requirement. I've expanded the answer.
@Angew sizeof tell the alignement requirement for primitive types only
@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.
@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.
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] |--------||--------||--------||--------||--------||--------||--------||--------| 

7 Comments

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)?
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)
Can you explain more about struct vs elements alignment?
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)
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...)
|
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.

2 Comments

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.
@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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.