To start with, I'm going to ignore the fact that your code is engaging in some very significant undefined behavior, and jump straight to the part where in a UNIX/Windows environment, the behavior of your code is relatively predictable (if not standard compliant).
Your code is assuming that the layout of the version that uses uint64_t
for its second member looks like this (2 characters == 1 byte):
-1-+-+-+-5-+-+-+-+10-+-+-+-+15-+-+-+-+20-+-+-+-+25
11111111222222222222222233333333__________________
But in actuality, due to padding, it's getting laid out like this:
-1-+-+-+-5-+-+-+-+10-+-+-+-+15-+-+-+-+20-+-+-+-+25
11111111........222222222222222233333333__________
This means that when you're assigning values into the struct, you're getting values like this (assuming Little Endian, based on your results):
-1-+-+-+-5-+-+-+-+10-+-+-+-+15-+-+-+-+20-+-+-+-+25
03000000060000000000000009000000????????__________
This means the 6 is getting written into padding, and isn't readable when accessing the members directly. Meanwhile, the 9 is getting written inside m_num2
, and m_num3
is receiving complete garbage. 0x0000000009000000
in little-endian hexadecimal converts to 38654705664
in decimal, which is why that's what you get for your second value. And because the third value is garbage, it could be literally anything, and 3435973836
is just what you happened to get on this particular execution.
Now, back to the Undefined Behavior thing: this is why you shouldn't write code like this. Because the padding being used in this struct is implementation defined (for good reason), and it's bad to depend on behavior like this to ascertain the correctness of your code.
If you absolutely do need to depend on bit-twiddling things like this, there are a few things you should be doing:
- Use
char*
or uint8_t*
, not void*
: arithmetic on a void*
is not allowed in C++, and even though you're not technically performing arithmetic on a void*
in your code, you're still leaving your code in a kludgy state that would be vulnerable to code alterations that would perform a void*
arithmetic.
- Use idioms for accessing the specific offset of a given member: for example,
offsetof
.
A better version of your code looks like this:
#include<iostream>
#include<cstddef>
#include<cstdint>
struct testUint{
uint32_t m_num1;
uint64_t m_num2;
uint32_t m_num3;
};
int main() {
testUint test_uint1;
testUint test_uint2;
test_uint2.m_num1 = 3;
test_uint2.m_num2 = 6;
test_uint2.m_num3 = 9;
//Prefer reinterpret_cast, not raw C-style casts
uint8_t * p = reinterpret_cast<uint8_t*>(&test_uint1);
*reinterpret_cast<uint32_t*>(p + offsetof(testUint, m_num1)) = test_uint2.m_num1;
*reinterpret_cast<uint64_t*>(p + offsetof(testUint, m_num2)) = test_uint2.m_num2;
*reinterpret_cast<uint32_t*>(p + offsetof(testUint, m_num3)) = test_uint2.m_num3;
//Don't use 'using namespace std;'
std::cout << test_uint1.m_num1 << ' ' << test_uint1.m_num2 << ' ' << test_uint1.m_num3 << std::endl;
}
A much better solution finds a way to make the interface of testUint
visible to the scope where p
exists, and avoid this pointer casting altogether.