1

背景:我想知道如果我们通过char *缓冲区获取二进制数据,如何(手动)反序列化它们。

假设:作为一个最小的例子,我们将在这里考虑:

  • 我只有一个int通过char*缓冲区序列化。
  • 我想int从缓冲区取回原件。
  • sizeof(int) == 4在目标系统/平台上。
  • 目标系统/平台的字节序是little-endian

注意:这纯粹出于一般利益,因此我不想使用任何类似的东西,std::memcpy因为我们不会看到我遇到的奇怪行为。


测试:我已经构建了以下测试用例:

#include <iostream>
#include <bitset>

int main()
{
    // Create neg_num and neg_num_bytes then display them
    int neg_num(-5000);
    char * neg_num_bytes = reinterpret_cast<char*>(&neg_num);
    display(neg_num, neg_num_bytes);

    std::cout << '\n';

    // Create pos_num and pos_num_bytes then display them
    int pos_num(5000);
    char * pos_num_bytes = reinterpret_cast<char*>(&pos_num);
    display(pos_num, pos_num_bytes);

    std::cout << '\n';

    // Get neg_num back from neg_num_bytes through bitmask operations
    int neg_num_back = 0;
    for(std::size_t i = 0; i < sizeof neg_num; ++i)
        neg_num_back |= static_cast<int>(neg_num_bytes[i]) << CHAR_BIT*i; // For little-endian

    // Get pos_num back from pos_num_bytes through bitmask operations
    int pos_num_back = 0;
    for(std::size_t i = 0; i < sizeof pos_num; ++i)
        pos_num_back |= static_cast<int>(pos_num_bytes[i]) << CHAR_BIT*i; // For little-endian

    std::cout << "Reconstructed neg_num: " << neg_num_back << ": " << std::bitset<CHAR_BIT*sizeof neg_num_back>(neg_num_back);
    std::cout << "\nReconstructed pos_num: " << pos_num_back << ":  " << std::bitset<CHAR_BIT*sizeof pos_num_back>(pos_num_back) << std::endl;

    return 0;
}

其中display()定义为:

// Warning: num_bytes must have a size of sizeof(int)
void display(int num, char * num_bytes)
{
    std::cout << num << " (from int)  : " << std::bitset<CHAR_BIT*sizeof num>(num) << '\n';
    std::cout << num << " (from char*): ";
    for(std::size_t i = 0; i < sizeof num; ++i)
        std::cout << std::bitset<CHAR_BIT>(num_bytes[sizeof num -1 -i]); // For little-endian
    std::cout << std::endl;
}

我得到的输出是:

-5000 (from int)  : 11111111111111111110110001111000
-5000 (from char*): 11111111111111111110110001111000

5000 (from int)  : 00000000000000000001001110001000
5000 (from char*): 00000000000000000001001110001000

Reconstructed neg_num: -5000: 11111111111111111110110001111000
Reconstructed pos_num: -120:  11111111111111111111111110001000

我知道测试用例代码很难阅读。简单解释一下:

  • 我创建一个int.
  • 我创建了一个char*指向先前创建的第一个字节的数组int(以模拟我有一个真实的int存储在char*缓冲区中)。因此其大小为 4。
  • 我显示int及其二进制表示
  • 我显示int存储在char*缓冲区中的每个字节的 和 连接,以比较它们是否相同(由于字节序的目的,顺序相反)。
  • 尝试int从缓冲区中取回原始文件。
  • 我显示了重构int的以及它的二进制表示。

我对负值和正值都执行了这个过程。这就是为什么代码不那么可读的原因(对此感到抱歉)。


正如我们所看到的,负值可以成功重建,但它对正值不起作用(我预料5000到了,我得到了-120)。

我已经用其他几个负值和正值进行了测试,结论仍然相同,它适用于负数但对正数失败。

问题:我很难理解为什么将 4 连接charsint通过按位移位会改变char正数的值,而它们与负值保持不变?

当我们查看二进制表示时,我们可以看到重构的数字不是由char我连接的 s 组成的。

是否与 相关static_cast<int>?如果我删除它,积分提升规则无论如何都会隐式应用它。但我需要这样做,因为我需要将其转换为一个int,以免丢失班次的结果。
如果这是问题的核心,如何解决?


另外:有没有比按位移位更好的方法来取回值?不依赖于系统/平台字节序的东西。

也许这应该是另一个单独的问题。

4

2 回答 2

4

这里有两个主要影响结果的因素:

  • 类型char可以是有符号或无符号的,它是编译器的实现细节。
  • 当整数转换发生时,有符号值被符号扩展。

这里可能发生的char是在您的系统上和您的编译器上签名的。这意味着当您将字节转换为 anint并且设置了高位时,该值将被符号扩展(例如二进制10000001将被符号扩展为1111111111111111111111111000001)。

这当然会影响您的按位运算。

解决方案是使用显式无符号数据类型,即unsigned char. 我还建议您使用unsigned int(or uint32_t) 进行类型转换和数据的临时存储,并且仅将完整结果转换为 plain int

于 2019-10-17T12:08:29.583 回答
0

这是因为static_cast<int>(pos_num_bytes[i])在某些情况下会返回负整数。

如果您想查看问题,可以用这个替换最后一个循环:

for (std::size_t i = 0; i < sizeof pos_num; ++i)
{
    pos_num_back |= static_cast<int>(pos_num_bytes[i])  << CHAR_BIT * i; // For littel-endian
    std::cout << "\pos_num_back: " << std::bitset<CHAR_BIT * sizeof pos_num_back>(pos_num_back) << std::endl;
    std::cout << std::bitset<CHAR_BIT * sizeof pos_num_bytes[i]>(pos_num_bytes[i]) << std::endl;
    std::cout << std::bitset<CHAR_BIT * sizeof pos_num_back>(static_cast<int>(pos_num_bytes[i])) << std::endl;

};

或者你可以运行它以获得预期的结果?

// Get pos_num back from pos_num_bytes through bitmask operations
int pos_num_back = 0;
char* p_pos_num_back = (char*)(&pos_num_back);
for (std::size_t i = 0; i < sizeof pos_num; ++i)
{
    p_pos_num_back[i] |= pos_num_bytes[i];
};
于 2019-10-17T12:19:39.203 回答