4

Firstly, I understand byte padding in structs. But I have still a small test contain a double field in struct and I don't know how to explain this :

typedef struct {
    char a;
    double b;
}data;

typedef struct{
    char a;
    int b;
}single;

int main(){
    printf("%d\n",sizeof(double));
    printf("%d\n",sizeof(single));
    printf("%d\n",sizeof(data));
}

Through out this test, the answer is : 8 8 and 16.

Why this result make me thinking ?

By second test, we can see size of word on my machine is 4 bytes.

By first test, we can see size of double is 8 bytes.

So, at the struct data : the result should be 12 bytes : 4 bytes for char and 8 bytes for double.

But, I don't know why the result is 16 bytes. (So strange with me)

Please explain it for me, thanks :)

Ernest Friedman-Hill
  • 80,601
  • 10
  • 150
  • 186
hqt
  • 29,632
  • 51
  • 171
  • 250

5 Answers5

8

It's sixteen bytes so that if you have an array of datas, the double values can all be aligned on 8-byte boundaries. Aligning data properly in memory can make a big difference in performance. Misaligned data can be slower to operate on, and slower to fetch and store.

Ernest Friedman-Hill
  • 80,601
  • 10
  • 150
  • 186
  • 1
    You might also want to add that in the `single` struct, the size is 8 because of the alignment for `int`. The size of a `char` is probably 1, but padding accounts for the other three bytes. – Dennis Meng Aug 10 '12 at 17:34
  • Performance is not the only reason to align data. On many machines, loading or storing to unaligned addresses causes exceptions, and many operating systems will not emulate them, so your program crashes. – Eric Postpischil Aug 10 '12 at 17:44
  • can you explain for me more about term `align data`. thanks :) – hqt Aug 10 '12 at 17:44
  • Basically, for primitive types, it's typically stored such that the memory address is a multiple of its size. In structs, that sometimes means adding padding here and there to keep that invariant. – Dennis Meng Aug 10 '12 at 17:48
  • (At least, the architecture I'm using to working with stored its variables like that. Regardless, byte padding is used to make sure that the variables are stored at multiples of whatever the architecture is looking for. Thus, we "align the data" to that multiple) – Dennis Meng Aug 10 '12 at 17:55
5

The procedure typically used for laying out data in a struct is essentially this:

  • Set Offset = 0.
  • For each member in the struct: Let A be its alignment requirement (e.g., 1, 2, 4, or 8 bytes, possibly more). Add to Offset the number of bytes needed to make it a multiple of A. (Given that A is a power of two, this can be done with Offset += -Offset & A-1, assuming two’s complement for the negation.) Assign the current value of Offset to be the offset of this member. Add the size of the member to Offset.
  • After processing all members: Let A be the greatest alignment requirement of any member. Add to Offset the number of bytes needed to make it a multiple of A. This final value of Offset is the size of the struct.

As Earnest Friedman-Hill stated, the last step adds padding to the end of the struct so that, in an array of them, each struct begins at the required alignment.

So, for a struct such as struct { char c; double d; int32_t i; }, on a typical implementation, you have:

  • Set Offset to 0.
  • char requires alignment of 1, so Offset is already a multiple of 1 (0•1 is 0). Put c at this offset, 0. Add the size of c, 1, to Offset, making it 1.
  • double requires alignment of 8, so add 7 to Offset, making it 8. Put d at this offset, 8. Add the size of d, 8, to Offset, making it 16.
  • int requires alignment of 4, so Offset is already a multiple of 4 (4•4 is 16). Put i at this offset, 16. Add the size of i, 4, to Offset, making it 20.
  • At the end, the largest alignment required was 8, so add 4 to Offset, making it 24. The size of this struct is 24 bytes.

Observe that the above has nothing to do with any word size of the machine. It only uses the alignment requirement of each member. The alignment requirement can be different for each type, and it can be different from the size of the type, and the above will still work.

(The algorithm breaks if alignment requirements are not powers of two. That could be fixed by making the last step increase the offset to be a multiple of the least common multiple of all the alignments.)

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Wow. amazing !!! Your post is nice. It also makes me understand other post before. :) Last word, can you tell me more a bit about last step: `Add to Offset the number of bytes needed to make it a multiple of A`. Why it will make processing data more effectively ? Thanks :) – hqt Aug 10 '12 at 18:18
  • 1
    It's not to make "processing data more effectively". It's to satisfy an absolute constraint that cannot be broken. If a type has a required alignment, it can never exist except at that alignment. In some cases the underlying machine has no such restriction, but the C implementation still makes such a restriction for arbitrary reasons such as supporting other machine variants that do have the restriction, or overcoming performance issues with misaligned data on the underlying machine. – R.. GitHub STOP HELPING ICE Aug 10 '12 at 18:24
  • 1
    @hqt: When you create an array of struct, the elements of the array are spaced at distances equal to the size of the struct. If that size is not a multiple of the greatest alignment requirement, then the member needing that alignment will not be at an aligned location. E.g., if the size of the example struct were 20 bytes, then the first element would be at offset 0, with members (c, d, i) at offsets 0, 8, and 16, which is fine. But the second element of the array would be at offset 20, with members at offset 20, 28, and 32. Since 28 is not a multiple of 8, the member d is not aligned. – Eric Postpischil Aug 10 '12 at 18:40
3

What is strange to you (or I'm missing something)?

The logic is the same (the padding is according to the "biggest" primitive field in the struct (I mean - int, double, char, etc.))

As in single, you have

1 (sizeof(char)) + 3 (padding) + 4 (sizeof(int))

it's the same in data:

1 (sizeof(char)) + 
7 (padding, it's sizeof(double) - sizeof(char)) + 
8 (sizeof(double))

which is 16.

Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
  • Sorry, I don't think byte padding is according to the biggest field. I think it build on word : an unit of CPU. it will always make a field fit in a word. and because word is 4 bytes (my machine), so the first four bytes is for char(3 bytes padding), and double will fit it two words. --> Sum : 12 – hqt Aug 10 '12 at 17:36
  • 1
    @hqt: The word size of the machine is not the same as the alignment that is required. First, a machine might have several word sizes, in the sense of what size of data is useful for various purposes. A machine might have 4-byte general registers, so 32-bit integers are good for arithmetic, at the same time that it has an 8-byte wide bus, so 64 bits is good for loading and storing. Second, the required alignment for each data type depends on the load and store instructions for that data type. Those load and store instructions might require 4-byte alignment, 8-byte alignment, or something else. – Eric Postpischil Aug 10 '12 at 17:47
  • @hqt - exactly, Eric's right. Padding and alignment are not the same. – Kiril Kirov Aug 10 '12 at 18:22
2

The compiler probably aligns all structure sizes to be a multiple of 8

Gir
  • 839
  • 5
  • 11
  • Can you explain for me more, please. I don't get your idea very much. thanks :) – hqt Aug 10 '12 at 17:37
  • Ernest explained it better. The compiler pads the struct to improve performance. If you had an array of objects of size 12. Lets say the first one would be at 0, the second will be at 12 - which is not a multiple of 8 - which could slow down the memory access - this depends on he platform (it could generate 2 separate memory reads to fetch that number). – Gir Aug 10 '12 at 17:43
2

Alignment is up to the compiler unless you explicitly specify it using compiler specific directives.

Variables aren't necessarily word aligned. Sometimes they're double word aligned for efficieny. In the particular case of floating points, they can be aligned to even higher values so that SSE will work.

Antimony
  • 37,781
  • 10
  • 100
  • 107