通过 char* 缓冲区读取 int 的行为是不同的,无论它是正数还是负数
Reading an int through char* buffer behaves different whether it is positive or negative
背景: 我想知道如果我们通过 char *
缓冲区获取二进制数据,如何(手动)反序列化它们。
假设:作为一个最小的例子,我们将在这里考虑:
- 我只有一个
int
通过 char*
缓冲区序列化。
- 我想从缓冲区中取回原来的
int
。
sizeof(int) == 4
目标 system/platform.
- 目标 system/platform 的字节顺序是 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 chars
连接成 int
会改变 char
正数与负值保持不变时的值 ?
当我们查看二进制表示时,我们可以看到重构的数字不是由我连接的 char
组成的。
和static_cast<int>
有关系吗?如果我删除它,积分提升规则将隐式应用它。但我需要这样做,因为我需要将其转换为 int
,以免丢失移位结果。
如果这是问题的核心,如何解决?
另外:有没有比按位移位更好的取回值的方法?不依赖于 system/platform.
字节序的东西
也许这应该是另一个单独的问题。
有两个主要因素会影响这里的结果:
- 类型
char
可以是有符号或无符号的,这是留给编译器的实现细节。
- 进行整数转换时,有符号值会进行符号扩展。
这里可能发生的是 char
在您的系统和您的编译器上签名。这意味着当您将字节转换为 int
并设置高位时,该值将被符号扩展(例如二进制 10000001
将被符号扩展为 1111111111111111111111111000001
)。
这当然会影响你的位运算。
解决方案是使用显式 unsigned 数据类型,即 unsigned char
。我还建议您使用 unsigned int
(或 uint32_t
)进行类型转换和数据的临时存储,并且只将完整结果转换为纯 int
.
这是因为 static_cast<int>(pos_num_bytes[i])
在某些情况下会 return 负整数。
如果您想查看问题,可以用这个替换最后一个循环:
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];
};
背景: 我想知道如果我们通过 char *
缓冲区获取二进制数据,如何(手动)反序列化它们。
假设:作为一个最小的例子,我们将在这里考虑:
- 我只有一个
int
通过char*
缓冲区序列化。 - 我想从缓冲区中取回原来的
int
。 sizeof(int) == 4
目标 system/platform.- 目标 system/platform 的字节顺序是 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 chars
连接成 int
会改变 char
正数与负值保持不变时的值 ?
当我们查看二进制表示时,我们可以看到重构的数字不是由我连接的 char
组成的。
和static_cast<int>
有关系吗?如果我删除它,积分提升规则将隐式应用它。但我需要这样做,因为我需要将其转换为 int
,以免丢失移位结果。
如果这是问题的核心,如何解决?
另外:有没有比按位移位更好的取回值的方法?不依赖于 system/platform.
字节序的东西也许这应该是另一个单独的问题。
有两个主要因素会影响这里的结果:
- 类型
char
可以是有符号或无符号的,这是留给编译器的实现细节。 - 进行整数转换时,有符号值会进行符号扩展。
这里可能发生的是 char
在您的系统和您的编译器上签名。这意味着当您将字节转换为 int
并设置高位时,该值将被符号扩展(例如二进制 10000001
将被符号扩展为 1111111111111111111111111000001
)。
这当然会影响你的位运算。
解决方案是使用显式 unsigned 数据类型,即 unsigned char
。我还建议您使用 unsigned int
(或 uint32_t
)进行类型转换和数据的临时存储,并且只将完整结果转换为纯 int
.
这是因为 static_cast<int>(pos_num_bytes[i])
在某些情况下会 return 负整数。
如果您想查看问题,可以用这个替换最后一个循环:
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];
};