递增void指针不能正确设置值

时间:2018-07-11 18:42:40

标签: c++

我正在尝试使用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。

我不太确定为什么会这样,因为我相信自己会增加适当的字节数。

1 个答案:

答案 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的范围内可见,并避免完全释放该指针。