1. I generally arrange largest to smallest members in a struct. That means that 1-byte padding after the i would not occur. 2. But there is other padding as well. There is often padding at the end of the struct. This is so, if you have an array of this struct, the next item will start on the proper boundary.
This was a nice opportunity to talk about bitfields as well and how the behave under alignment. We use them a lot at work (these 1-bit bitfields are a pain) and I've been bitten before due to alignment requirements of bitfields...
@Jason, why not start with the biggest type first? By starting with the biggest you always have the most optimal struct size right? 8+4+4+1+1+1 (+1 for alignment here) = 20 or 24
I don't usually use packed structs... *if I'm in control of the struct definition*. I usually manually pack the struct and static_assert() the size is what I manually calculated, this comes up a *lot* in Vulkan/OpenGL/ anything that touches GLSL, where default alignment rules are not like C++, and default to *large* padding amounts. In the case of OpenGL/Vulkan code, I will often actually manually insert padding variables because those rules don't line up with C++. For a lot of other uses, in particular *data serialization*, I'm forced to pack structures, because the structures are *defined* to be packed. There's a data format I have to use a lot, that *must be packed* when over the wire or stored via io, otherwise none of the functions de-serializing it will work. It's sad, because the structure *could* have been re-arranged such that packing was unnecessary, though the order of elements may not have been as human friendly.
Hey Jason ! I was actually waiting for a good video from you about this matter I do use packed struct - but as part of multiple embedded environments. I would be happy share my thoughts and takes and learn from that experience.
A quick fix is simply to start the struct with the largest type and continue with the smallest type. If we assume every type is a power of two in size, you can prove that you will have the less padding this way.
X86 can access the first x bytes in the struct faster than later bytes, also sort by how important the member is. (I don't remember what x is, 256 perhaps)
@@LeDabeI was expecting him to place the long long first, then the ints, and then the chars. I can't test it now, but in this way would it be possible to get 19 bytes?
I've always put the smallest ones at the end. I'm not sure which is better in general. If you have them in an array of course, the struct has to be padded out to the correct alignment for the most constrained element in it to be on the right boundary, and so I think that negates any advantage for ascending vs. descending size order.
This may seem like a minor issue, and it could be in a lot of projects (how many instances are you going to have of this particular struct?) but in some applications you might have millions of these you need to allocate and iterate, and in those the order of the fields is absolutely critical. And saving space isn't always better- due to the way memory access works, you might want to put members that are accessed together frequently next to each other even though it means the compiler injects a lot of padding.
to avoid padded warnings you can introduce "char padding" members in your struct, to fill the gaps. this also allow for more easy knowing of were you can put new members in this struct wo breaking the alignment
I believe that when you arrange in largest-to-smallest way, you always get the best result. Because every smaller-to-larger pair causes padding and larger-to-smaller (or same-to-same) not. And end-of-the-struct padding with proper ordering consequently will be at least not bigger than total padding for any other member permutation case.
I'm curious about this as well. It seems like it'd be something trivial for the compiler to optimize. I mean, I certainly don't care if the compiler re-arranged the ordering of class members so they'll use less space.
C++23 requires that the order of the members in memory to match the order in the definition. Earlier standards were a bit looser and allowed members with different access control to be re-ordered.
Arranging the values from largest size to smallest size, then no padding would ever be used between values (64bit at the top, followed by 32bit, then 16bit, then 8bit). There could still be padding at the end when you have the structs in an array, to make sure the alignment of the next struct is correct. All this about alignment and padding is something i've had to deal with a bit while learning to use the Vulkan API, since the GPU doesn't handle it for you at all.
Your manipulation of tabular data had me wondering if CE could do blockwise selections, since it has a limited subset of Vim keybindings, and apparently it can. So, just a tiny tip for you for next time you want to demonstrate editing a table of data, such as comments at the end of every line, you can just hit ctrl+v and select the entire block at once then x or d to delete it. You just need to enable Vim keybindings in the interface.
You don't want to use them, as I understand it. std::alignment_of was created before `alignof` existed, but now `alignof` supersedes it in all ways, though it still works fine. std::aligned_storage in fact can't alias for other objects like an alignas(T) std::byte[sizeof(T)] could, so using std::aligned_storage is probably in fact an error
I might be wrong, but ~5:54 in line 20 the comment says that the char is in byte 25. Isn't it 24? char a; // [0, 1) (1) // padding // [1, 4) (3) int i; // [4, 8) (4) char b; // [8, 9) (1) // padding // [9, 12) (3) int j; // [12, 16) (4) long long l; // [16, 24) (8) char c; // [24, 25) (1)
padding issues are solved largely by Structures of Arrays programming method, speed increase even more dramatic than worrying about complicated structure concepts.
PowerPC had really great support for unaligned writes... except for the Wii which shipped with a hardware bug that corrupts unaligned writes to uncached memory! It's usually not worth the risk to invoke the terror of unaligned writes on any platform.
Not at all - size_t means "native machine size type" this is the correct thing to do for crossplatform code. int32, etc, should be used when I want to specify the exact type right now.
@@cppweekly Rather than using c-style data types that aren't quite so clear about their properties, which you describe in your video. Instead, if you want to clarify them - which is always - you use std data types and constructs. std::size_t is not designed to be portable, it's supposed to be the largest possible unsigned integer-type the system supports. std::size_t is entirely system dependent by design.
Most of the time address 0x00 is special and not used for normal allocation, thus it is overloaded as the null pointer address. There are exceptions though where derefrencing a pointer to 0x00 is a valid thing to do and not the same as trying to deref a null pointer. Really a lot of systems avoid the whole thing with special handling of 0x00 page in the virtual address mapping. (boilerplate disclaimer: If either special alignment or access to 0x00 really matters then double check the manuals for your system; hardware and OS) Though I can see how 0x00 being considered special could lead to assumptions that create an off by one alignment error.
The additional episodes list grows fast in the other comment. If not already done, please add _packing_ (+ endianess) I'm using this a lot in an embedded context. But I'm sure I'm missing some concepts, and you are certainly one of the most skilled to teach us !
It is, in fact, impossible to keep track of the topic requests in the video comments! I have a tracker here where you can add your request so it won't get lost - github.com/lefticus/cpp_weekly/issues/
@@cppweekly Done Maybe you should give this link in every video you publish, in the description or a pined comment. Anyway, a lot of thanks for your time and for making us less dumb !
Isn't packed structs for interfacing with odd-sized binary formats? The question is then if you should save values to an unpacked version of the same struct when loading data, or keep it as is despite a potentially slower access to individual fields.
1:49 It's important to note that alignment of types is architecture dependent. 7:03 Such behavior was inherited from C and isn't that great because the order of fields that is useful for humans isn't particularly good for the compiler. I find that the Rust's behavior is better because there the order of fields in the binary representation is decided by the compiler unless the programmer opts into doing this manually. There is also a very important overlooked topic - trailing padding with nested structs. It can highlight the importance of compile-time reflection and maybe you can demonstrate a workaround with boost/pfr.
Must is not correct. x86 is a byte addressable machine. There is no alignment restrictions on basic types. (char, short, int, float, double, long long). If they do cross their alignment, there is at most a load delay if the cpu crosses a cache line boundary. (64 bytes) In previous generations (sandy bridge and before) vector instructions (SSE/AVX) would fault if they were not aligned to a 128bit boundary, this has been relaxed since then and they will not fault as long as they are aligned to a dword. Regardless of all this it is good practice to align data. It matters on older hardware and ARM/RISC/GPU can often require greater alignment. As for what the user has to consider. For modern OSs, all heap allocations are cache aligned at 64bytes, stack primitives are often aligned as well, so if you are doing vector processing you need not declare alignment as you did before. As for manual ordering, it is best and easiest to put the largest types first, then the rest will fall into place. If you put smaller primitives first you have to group them to align with the latter elements or padding will be introduced.
UA-cam will block any comments with links to outside websites without exception. I'm not even sure if links to other UA-cam videos always make it through the filter.
I usually prefer to add explicit padding to the end of my structs to make the wasted space explicit and visible. struct S { char c; char _pad[3]; int i; long long l; };
You should have reversed the order of your fields in fact. As a rule of thumb, first should go all the long longs, then all ints, then all short ints (if you have them), then all chars/bytes/bools (if the size of bool is 1 byte). That is if you care about total size of your structs / performance so much. But in most cases this is largely irrelevant, in other words you should only care when you start to see problems.
That's a compiler-specific pragma, and I'm guessing it makes the code less efficient. I have done that in network protocol work, where you `reinterpret_cast()` a blob of data into a struct.
Historically I've also sorted stack variables to be largest to smallest size; I don't know whether the compiler is smart enough to rearrange them if declared less than optimally.
On a modern compiler your stack objects are probably just residing in registers anyhow, or eliminated entirely. Your stack variables should be ordered in the way it logically makes sense to use and initialize them, and they should be limited in scope as much as possible usually.
I use packed structs but only for structs that are written to disk. If they are not, I use the default packing. Usually this is achieved via #pragma pack.
I would recommend not doing that. It is ok if you know your compiler, your platform, your cpu and don't need any sort of portability (the same computer will read and write the file) and are certain you won't run into alignment, packing or other issues no matter what happens in the future. Otherwise, I'd manually read/write the bytes in the proper order with the proper packing for the file. Something like: // write 32-bit little endian buf[0] = (value >> 0) && 0xFF; buf[1] = (value >> 8) && 0xFF; buf[2] = (value >> 16) && 0xFF; buf[3] = (value >> 24) && 0xFF; // read 32-bit little endian value = (buf[0]
Wtf? Pack will address exact struct representation of physical memory address for devices and physical address of IO ports, registers, flags....!! Nothing to do with c++!!!!!!!! Wtf? Pack is to get out of c++ normal addressing data in memory (natural integer and address boundaries padding)
One tip when rearranging data members to avoid padding: Put your biggest members FIRST.
Dude Jason was driving me nuts in this episode by doing the complete opposite of this!
Exactly
Next episode: what are cachelines and why do want to align for them?
BEST comment and idea
2nd next episode: what is endianness or byte order. Also, the monstrosity of the past - mixed-endianness.
1. I generally arrange largest to smallest members in a struct. That means that 1-byte padding after the i would not occur.
2. But there is other padding as well. There is often padding at the end of the struct. This is so, if you have an array of this struct, the next item will start on the proper boundary.
By largest to smallest members, I mean by type. So largest type first, smallest type last.
This was a nice opportunity to talk about bitfields as well and how the behave under alignment. We use them a lot at work (these 1-bit bitfields are a pain) and I've been bitten before due to alignment requirements of bitfields...
@Jason, why not start with the biggest type first? By starting with the biggest you always have the most optimal struct size right? 8+4+4+1+1+1 (+1 for alignment here) = 20 or 24
Never mind. Yours will also work. I was thinking that packing biggest first was a best practice for cache. Seems both way works
Yes but your solution prevent the padded warning for clang on the fields and instead put it on the struct
I don't usually use packed structs... *if I'm in control of the struct definition*. I usually manually pack the struct and static_assert() the size is what I manually calculated, this comes up a *lot* in Vulkan/OpenGL/ anything that touches GLSL, where default alignment rules are not like C++, and default to *large* padding amounts. In the case of OpenGL/Vulkan code, I will often actually manually insert padding variables because those rules don't line up with C++. For a lot of other uses, in particular *data serialization*, I'm forced to pack structures, because the structures are *defined* to be packed. There's a data format I have to use a lot, that *must be packed* when over the wire or stored via io, otherwise none of the functions de-serializing it will work. It's sad, because the structure *could* have been re-arranged such that packing was unnecessary, though the order of elements may not have been as human friendly.
Hey Jason ! I was actually waiting for a good video from you about this matter
I do use packed struct - but as part of multiple embedded environments.
I would be happy share my thoughts and takes and learn from that experience.
A quick fix is simply to start the struct with the largest type and continue with the smallest type. If we assume every type is a power of two in size, you can prove that you will have the less padding this way.
so does sorting the values from smallest to largest AFAIK
X86 can access the first x bytes in the struct faster than later bytes, also sort by how important the member is. (I don't remember what x is, 256 perhaps)
@@LeDabeI was expecting him to place the long long first, then the ints, and then the chars. I can't test it now, but in this way would it be possible to get 19 bytes?
@@MirralisDias i think you have to be aligned on 8 bytes (64bit OS) so it will be also 24 bytes
it seems to me that the general rule is that you can do small to large or large to small or any “std::rotate” :) of that. no?
Clang tidy will also detect padding issues if you can’t use Wpadded. Apologies if this was said I’m in a noisy environment
I've always put the smallest ones at the end. I'm not sure which is better in general. If you have them in an array of course, the struct has to be padded out to the correct alignment for the most constrained element in it to be on the right boundary, and so I think that negates any advantage for ascending vs. descending size order.
I think I'm using packed structs correctly, by not using it 😃
Same thing here :)
This may seem like a minor issue, and it could be in a lot of projects (how many instances are you going to have of this particular struct?) but in some applications you might have millions of these you need to allocate and iterate, and in those the order of the fields is absolutely critical. And saving space isn't always better- due to the way memory access works, you might want to put members that are accessed together frequently next to each other even though it means the compiler injects a lot of padding.
to avoid padded warnings you can introduce "char padding" members in your struct, to fill the gaps. this also allow for more easy knowing of were you can put new members in this struct wo breaking the alignment
I believe that when you arrange in largest-to-smallest way, you always get the best result. Because every smaller-to-larger pair causes padding and larger-to-smaller (or same-to-same) not. And end-of-the-struct padding with proper ordering consequently will be at least not bigger than total padding for any other member permutation case.
Thank you, this was an excellent explanation.
Is there any way to tell the compiler to automatically reorder struct members for the smallest possible size?
I'm curious about this as well. It seems like it'd be something trivial for the compiler to optimize. I mean, I certainly don't care if the compiler re-arranged the ordering of class members so they'll use less space.
C++23 requires that the order of the members in memory to match the order in the definition. Earlier standards were a bit looser and allowed members with different access control to be re-ordered.
@@imtootired4122 But no one did
Arranging the values from largest size to smallest size, then no padding would ever be used between values (64bit at the top, followed by 32bit, then 16bit, then 8bit). There could still be padding at the end when you have the structs in an array, to make sure the alignment of the next struct is correct.
All this about alignment and padding is something i've had to deal with a bit while learning to use the Vulkan API, since the GPU doesn't handle it for you at all.
Add data memebers in descending order of their size- then you will get optimal padding
Your manipulation of tabular data had me wondering if CE could do blockwise selections, since it has a limited subset of Vim keybindings, and apparently it can. So, just a tiny tip for you for next time you want to demonstrate editing a table of data, such as comments at the end of every line, you can just hit ctrl+v and select the entire block at once then x or d to delete it. You just need to enable Vim keybindings in the interface.
Anyone has an idea about why do you ever want to use std::alignment_of and use std::aligned_storage instead of alignof and alignas?
You don't want to use them, as I understand it. std::alignment_of was created before `alignof` existed, but now `alignof` supersedes it in all ways, though it still works fine. std::aligned_storage in fact can't alias for other objects like an alignas(T) std::byte[sizeof(T)] could, so using std::aligned_storage is probably in fact an error
You probabbly wouldn't want to use std::aligned_storage in greenfield code now since its depricated in cpp23+
You probabbly wouldn't want to use std::aligned_storage in greenfield code now since its depricated in cpp23+
I might be wrong, but ~5:54 in line 20 the comment says that the char is in byte 25. Isn't it 24?
char a; // [0, 1) (1)
// padding // [1, 4) (3)
int i; // [4, 8) (4)
char b; // [8, 9) (1)
// padding // [9, 12) (3)
int j; // [12, 16) (4)
long long l; // [16, 24) (8)
char c; // [24, 25) (1)
I probably did get something wrong, yeah.
@@cppweekly thanks for your reply. I’m shocked that you take the time :O
Thank you for all the great videos! I learn so much from them.
@@roblauer7568 Why is the total byte usage here 32 and not 25? Is it because we have to store things in multiples of 8?
Does the last char not start on byte 24?
padding issues are solved largely by Structures of Arrays programming method, speed increase even more dramatic than worrying about complicated structure concepts.
Any sources to really understand the deep stuff? Why this alignment gives optimization?
Not only can unaligned access be slower, but it can also be simply disallowed by the hardware and cause a crash.
PowerPC had really great support for unaligned writes... except for the Wii which shipped with a hardware bug that corrupts unaligned writes to uncached memory! It's usually not worth the risk to invoke the terror of unaligned writes on any platform.
first of all, I'd more than likely be using items like std::uint16_t or std::uint32_t, and then std::size_t. Your code appears depreciated.
Not at all - size_t means "native machine size type" this is the correct thing to do for crossplatform code. int32, etc, should be used when I want to specify the exact type right now.
@@cppweekly Rather than using c-style data types that aren't quite so clear about their properties, which you describe in your video. Instead, if you want to clarify them - which is always - you use std data types and constructs. std::size_t is not designed to be portable, it's supposed to be the largest possible unsigned integer-type the system supports. std::size_t is entirely system dependent by design.
I'm just curious about if the compiler can do the rearrangement for us automatically, since it's a seemingly simple optimization
Absolutely not, because it would break ABI compatibility
Most of the time address 0x00 is special and not used for normal allocation, thus it is overloaded as the null pointer address. There are exceptions though where derefrencing a pointer to 0x00 is a valid thing to do and not the same as trying to deref a null pointer. Really a lot of systems avoid the whole thing with special handling of 0x00 page in the virtual address mapping.
(boilerplate disclaimer: If either special alignment or access to 0x00 really matters then double check the manuals for your system; hardware and OS)
Though I can see how 0x00 being considered special could lead to assumptions that create an off by one alignment error.
The additional episodes list grows fast in the other comment.
If not already done, please add _packing_ (+ endianess)
I'm using this a lot in an embedded context. But I'm sure I'm missing some concepts, and you are certainly one of the most skilled to teach us !
It is, in fact, impossible to keep track of the topic requests in the video comments! I have a tracker here where you can add your request so it won't get lost - github.com/lefticus/cpp_weekly/issues/
@@cppweekly Done
Maybe you should give this link in every video you publish, in the description or a pined comment.
Anyway, a lot of thanks for your time and for making us less dumb !
I think you should only care in critical parts of code like Vertex structs in game development etc
Isn't packed structs for interfacing with odd-sized binary formats? The question is then if you should save values to an unpacked version of the same struct when loading data, or keep it as is despite a potentially slower access to individual fields.
1:49 It's important to note that alignment of types is architecture dependent.
7:03 Such behavior was inherited from C and isn't that great because the order of fields that is useful for humans isn't particularly good for the compiler. I find that the Rust's behavior is better because there the order of fields in the binary representation is decided by the compiler unless the programmer opts into doing this manually.
There is also a very important overlooked topic - trailing padding with nested structs. It can highlight the importance of compile-time reflection and maybe you can demonstrate a workaround with boost/pfr.
Must is not correct. x86 is a byte addressable machine. There is no alignment restrictions on basic types. (char, short, int, float, double, long long). If they do cross their alignment, there is at most a load delay if the cpu crosses a cache line boundary. (64 bytes) In previous generations (sandy bridge and before) vector instructions (SSE/AVX) would fault if they were not aligned to a 128bit boundary, this has been relaxed since then and they will not fault as long as they are aligned to a dword. Regardless of all this it is good practice to align data. It matters on older hardware and ARM/RISC/GPU can often require greater alignment. As for what the user has to consider. For modern OSs, all heap allocations are cache aligned at 64bytes, stack primitives are often aligned as well, so if you are doing vector processing you need not declare alignment as you did before. As for manual ordering, it is best and easiest to put the largest types first, then the rest will fall into place. If you put smaller primitives first you have to group them to align with the latter elements or padding will be introduced.
i use packed structs to send data via serial protocol, is that correct?
You've probably got undefined behavior on the other end, if you're using reinterpret_cast or a c-style cast.
visual studio has a new feature to visualize class layout
apparently I'm not allowed to post links
oh, we are sponsored by CLion ;-)
UA-cam will block any comments with links to outside websites without exception. I'm not even sure if links to other UA-cam videos always make it through the filter.
@@Minty_Meeo I often comment with links to videos, so that's fine
I usually prefer to add explicit padding to the end of my structs to make the wasted space explicit and visible.
struct S {
char c;
char _pad[3];
int i;
long long l;
};
this is ok, but not if you care about aggregate initialization. perhaps named field initialization is a workaround now, I haven’t tried.
@@AlfredoCorrea true
@@alpergunes6380 Perhaps a hypothetical "hidden" attribute could solve this. `hidden: char _pad[3];`
You should have reversed the order of your fields in fact. As a rule of thumb, first should go all the long longs, then all ints, then all short ints (if you have them), then all chars/bytes/bools (if the size of bool is 1 byte). That is if you care about total size of your structs / performance so much. But in most cases this is largely irrelevant, in other words you should only care when you start to see problems.
Why doesn't compiler do anything about the padding? I would've intuitively set long long first, then ints and then chars.
wrapping the struct with "#pragma pack(1)" will make it compile
That's a compiler-specific pragma, and I'm guessing it makes the code less efficient. I have done that in network protocol work, where you `reinterpret_cast()` a blob of data into a struct.
#pragma pack(push, 1)
struct X {×××} ;
#pragma pack(pop)
This is also useful to read binary files that have well defined headers
Historically I've also sorted stack variables to be largest to smallest size; I don't know whether the compiler is smart enough to rearrange them if declared less than optimally.
On a modern compiler your stack objects are probably just residing in registers anyhow, or eliminated entirely.
Your stack variables should be ordered in the way it logically makes sense to use and initialize them, and they should be limited in scope as much as possible usually.
I use packed structs but only for structs that are written to disk. If they are not, I use the default packing. Usually this is achieved via #pragma pack.
I would recommend not doing that. It is ok if you know your compiler, your platform, your cpu and don't need any sort of portability (the same computer will read and write the file) and are certain you won't run into alignment, packing or other issues no matter what happens in the future.
Otherwise, I'd manually read/write the bytes in the proper order with the proper packing for the file. Something like:
// write 32-bit little endian
buf[0] = (value >> 0) && 0xFF;
buf[1] = (value >> 8) && 0xFF;
buf[2] = (value >> 16) && 0xFF;
buf[3] = (value >> 24) && 0xFF;
// read 32-bit little endian
value = (buf[0]
GCC 13.2 supports -Wpadded (cannot try earlier versions at the moment), but as warnings and is not as informative as clang.
Testing with compiler explorer, GCC supports -Wpadded at least back to version 4.1.2.
@@paulfee9839 It means that GCC supported -Wpadded before clang initial release. Pure free useless GCC bashing at 7:18.
Absolutely bizarre that there was no mention of the `ailgnof` operator.
Are you seriously suggesting that I, someone who is watching C++ educational content, might be making mistakes in C++? >:(
Wtf? Pack will address exact struct representation of physical memory address for devices and physical address of IO ports, registers, flags....!!
Nothing to do with c++!!!!!!!! Wtf? Pack is to get out of c++ normal addressing data in memory (natural integer and address boundaries padding)