我正在尝试使用void指针将一个结构对象的值设置为等于相同类型的另一个结构对象。使用不同大小的uint时,这些值未按预期显示。
一个简单的例子演示了这个问题,假设我有一个struct:
struct testUint{
uint32 m_num1;
uint32 m_num2;
uint32 m_num3;
};
我主要是做
testUint test_uint1;
testUint test_uint2;
test_uint2.m_num1 = 3;
test_uint2.m_num2 = 6;
test_uint2.m_num3 = 9;
void* p = &test_uint1;
*(uint32*) p = test_uint2.m_num1;
p = (uint32*)p+1;
*(uint32*) p = test_uint2.m_num2;
p = (uint32*)p+1;
*(uint32*) p = test_uint2.m_num3;
p = (uint32*)p+1;
cout << test_uint1.m_num1 << test_uint1.m_num2 << test_uint1.m_num3 <<endl;
输出预期为:3、6和9
但是当我将结构更改为:
struct testUint{
uint32 m_num1;
uint64 m_num2;
uint32 m_num3;
};
并保留与以前相同的代码,只是将中间部分更改为:
*(uint32*) p = test_uint2.m_num1;
p = (uint32*)p+1;
*(uint64*) p = test_uint2.m_num2;
p = (uint64*)p+1;
*(uint32*) p = test_uint2.m_num3;
p = (uint32*)p+1;
输出变为:3,38654705664和3435973836。
我不太确定为什么会这样,因为我相信自己会增加适当的字节数。
答案 0 :(得分:3)
首先,我将忽略以下事实:您的代码正在发生一些非常重要的未定义行为,而直接跳到UNIX / Windows环境中代码行为相对可预测的部分(如果不符合标准)。
您的代码假定使用uint64_t
作为第二个成员的版本的布局看起来像这样(2个字符== 1个字节):
-1-+-+-+-5-+-+-+-+10-+-+-+-+15-+-+-+-+20-+-+-+-+25
11111111222222222222222233333333__________________
但是实际上,由于填充的原因,它的布局如下:
-1-+-+-+-5-+-+-+-+10-+-+-+-+15-+-+-+-+20-+-+-+-+25
11111111........222222222222222233333333__________
这意味着,当您将值分配给结构时,您将获得这样的值(假设Little Endian,基于您的结果):
-1-+-+-+-5-+-+-+-+10-+-+-+-+15-+-+-+-+20-+-+-+-+25
03000000060000000000000009000000????????__________
这意味着6已被写入填充,并且在直接访问成员时不可读。同时,9正在m_num2
内编写,而m_num3
正在接收完整的垃圾。小尾数十六进制的0x0000000009000000
转换为十进制的38654705664
,这就是为什么要获得第二个值的原因。而且因为第三个值是垃圾,所以它实际上可以是任何东西,而3435973836
恰好是您在此特定执行中碰巧得到的东西。
现在,回到“未定义行为”:这就是为什么您不应该编写这样的代码的原因。因为在此结构中使用的填充是实现定义的(有充分的理由),所以依靠这样的行为来确定代码的正确性是很不好的。
如果您绝对需要依赖像这样的琐事,那么您应该做一些事情:
char*
或uint8_t*
而不是void*
:在C ++中不允许对void*
进行算术,即使您在技术上并未对{ {1}}在您的代码中,您仍然使代码处于混乱状态,容易受到执行void*
算术的代码更改的影响。offsetof
。更好的代码版本如下:
void*
更好的解决方案是找到一种方法,使#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;
}
的接口在存在testUint
的范围内可见,并避免完全释放该指针。