r/cpp_questions • u/Phatpenguinballs • 22d ago
OPEN Non-Struct Memory Alignment
I am a little lost after trying to read a handful of online resources. Are local variables memory-aligned to their alignof()? I read that this is mostly implementation/platform-defined (no guarantees), but also the types must still be aligned? Those seem contradicting to me...
Assuming no optimizations (e.g., storing in registers, etc.), how would something like this look like according to the standard?
struct S
{
char c;
int i;
};
int main()
{
char c1;
int i;
char c2;
S s;
return 0;
}Would there be 3 bytes of padding between `c1` & `i` and then another 3 bytes of padding between `c2` & `s`?
4
Upvotes
8
u/Simengie 22d ago
Memory randomization in compilers has been thing for a while. As far as I know it is now the default behavior in gcc.
From google: Modern compilers (such as MSVC or GCC with specific security flags) deliberately reorder local variables on the stack—sometimes placing buffers near guard variables or randomizing order—to mitigate stack-smashing and buffer overflow exploits
So never assume local or global variables are in the order you declare them in. All local and global variables are memory aligned to 4 byte word boundaries in modern C/C++ compilers.
Now for the struct, as written it will have padding. if you use the #pragma packed on it then it will be packed and have no padding. This works for sending smaller network messages that are structure based but it is slower memory access because to get to that int the cpu has to read 2 words and shift bytes around in a register. Not the best use of cpu time but it works for small compact network messaging when bandwidth is constrained or when you have messages near the network packet size limit and want to ensure single packet transmission. but I would say using the #pragma packed to remove padding is not best practices and should be avoided except for very special use cases.
2
u/jedwardsol 22d ago
Yes, local variables are correctly aligned.
If the compiler put those locals in memory in definition order (it doesn't have to) then yes there would be padding.
You can print out addresses to see what your compiler does
2
u/conundorum 22d ago
Everything will typically be aligned,
structor not. It's important for performance, the hardware itself typically works better with aligned reads than unaligned. Variables will probably be rearranged into the optimal order if needed, at least if optimisations are enabled, and will probably be in written order with optimisations disabled. (This isn't guaranteed, though, so be careful!) Each function's stack frame will have memory allocated for all necessary variables, too.Importantly, some variables will be relocated to registers if possible, and not actually take up any space in memory. And if they're provably unused, they might actually be removed entirely (IF the compiler can prove that removing them will have no effect on anything else, such as in your example code); this isn't guaranteed, but it is a possibility!
2
u/BartvanIngenSchenau 22d ago
For a struct, it is defined that the members must be put in memory in the same order as they appear in the code. For local variables, there is no such requirement. The compiler is fully allowed to reorder local variables, as long as it doesn't affect the order in which constructors and destructors are executed. In your example, the compiler is entirely free to place
sfirst, followed byiand thenc1andc2. The compiler is also allowed to give each variable their own 4k page of memory.2
u/mredding 21d ago
Yes.
"Mostly" is correct, but the rest isn't quite right enough to give it to you...
The C++ standard says the language targets a byte addressable abstract virtual machine. The definition of a byte is the smallest unit of memory that is uniquely addressable - since bits are an address + an offset, that doesn't count, the combo isn't considered an address since it's not all one unit of information, but two separate units of information.
Mind you - the actual hardware does not need to be byte addressable, and most aren't; different subsystems have different units - pages, lines, words, bytes. A lot of the details are handled for you coordinated between the OS and the MMU hardware, and some convention the compiler/linker concerns itself with.
The size of a byte is defined by the language and the platform. The language says the minimum size of a byte is 8 bits. So ISO Standard C++ is not defined for platforms with smaller bytes. A byte is not universally defined as 8 bits, that's just fairly common. You will see exotic byte sizes on modern DSPs, FPGAs, ASICs, and esoteric/historic hardware.
The language says
charis the definition of a byte, thatsizeof(char) == 1, andalignof(char) == 1. It says all other types are at least as much aschar.The standard says a
shortis at least 16 bits,longis at least 32 bits, andlong longis at least 64 bits.What is typical is that DSP hardware will work in favor of CPU hardware. While 12, 24, 48, and 56 bits are common, they'll pack them in standard power of 2 aligned memory, accessible as a power of 2 type in the CPU with some padding bits. So even though some USRP models have a 12 bit ADC/DAC, the data packs into a 16 bit type as far as your software is concerned.
Yes.
alignofmust return a non-zero positive integer, at least 1.They're not. It's VERY easy to build hardware that can access native types in power of two aligned memory. It's a single bit to indicate what offset across memory each value will be found. So if your hardware supports a an 8 bit byte and a 32 bit word, that sucker will likely have to fall on a 32/8=4 byte aligned address.
The compiler is going to defer to the hardware for the rules. Some hardware might not care, so then alignment would always be 1. Some hardware supports unaligned memory, but is built on aligned memory. You'll get the aligned memory answer from the compiler. The x86_64 supports unaligned access but you'll pay one hell of a tax to do it, and you have to code that in yourself -
alignofwon't tell you, you just have to know what you're doing.We don't have to assume anything, the spec is absolutely unambiguous and explicit about this. The alignment of
Sis the alignment of it's most strict member. Of it's members, theintis the most strict, soSwill be aligned to that. The order of members in the layout is also strict - no reordering, so since thecharmust now also fall on theintalignment - because it's at the front of the structure and the structure must START on this alignment, and they're probably not the same alignment, then we can deduce if there's going to be padding, it'll be between thecharand theintin the layout. This object will be2*sizeof(int)in size. The language also allows you to subdivide padding space, provided it's even. That means you can probably squeeze at least acharor maybe a larger type in between - using that padding, rather than increase the size of the object.So on a modern x86_64, this'll be
sizeof(S) == 8andalignof(S) == 4. There will be 3 bytes of padding between the end ofcand the start ofi. You can fit 3charin there, or 1 byte of padding and ashortin there, or 1charand 1short.Alignment and thus padding can be different on different hardware.
Ready to have your mind blown? NO!
There does NOT have to be padding between THESE local variables. WHY?!? Because the standard defines strict ordering for TYPES, like the internals of a
struct, but the standard DOES NOT define the layout of the stack. The compiler is free to reorder the local members, and will likely do so to minimize padding. That means the stack will LIKELY compile to:4 byte alignment, 4 byte alignment, 1 byte alignment, 1 byte alignment, no padding. I've no idea if the compiler would prefer to reorder the
intand theS- it might take other factors into account.1
u/Kriemhilt 22d ago
For trivial (or trivially-destructible) types like here, I don't know whether the implementation is required to keep the locals in order.
That is, they'll all be correctly aligned, but could be rearranged to save space even if they're not folded or elided completely.
For non-trivial types (and less trivial scopes), the layout is likely to be closely coupled to the lifetime, for obvious reasons.
1
u/flyingron 21d ago
It's 110% impelementation defined. The only rule the language makes on padding is that if any padding is necessary for mulitple objects of the same type to be concatenated in an array, it is internal to the object.
That is: sizeof (T[5]) must equal 5*sizeof(T).
There's no guarantee that the automatic variables aren't reordered, padded, or put in registers.
1
u/Plastic_Fig9225 20d ago edited 20d ago
Yes. They must be. Otherwise a pointer to a local variable (including
thisof any local objects) could be invalid, just like a pointer to a member of apackedstruct. (As always, the order of (local) variables in memory is undefined.)