您必须研究二进制表示和二进制数学才能真正理解按位运算对值的作用。
请注意,3500 很容易适合 16 位值,3500 小于 2^16。
如果你想使用有保证大小的类型,你必须使用uint8_t、uint16_t 和类似的。
如果您想要一个可移植的代码,在使用 C++ 的情况下,此类操作需要谨慎处理。 int 可能具有不同的大小甚至不同的字节顺序(字节序),但按位移位 >> 和 > 总是向低位移动。
请注意,在 C++ 中,所有按位运算的结果都被提升为 unsigned int 的大小或更大的类型(如果需要)。未定义带符号值的移位操作。
在所需算法的简单但安全的变体中,我们必须执行这些步骤。
(决定我们将字节写入缓冲区的顺序。假设我们从不重要到重要。)
- 确定要写入值的第一个字节,由
p 指向
- 以字节为单位确定写入值的大小,
pend 指针在写入值之后定义一个字节。
- 通过使用 AND 操作 (&) 和由全 1 组成的掩码“剪切”原始值的第一个字节,并将其分配给
p 指向的位置。
- 通过右移 (>>) 从值中删除写入的字节。
- 递增
p。
- 如果 (p != pend) 转到 3。
(可选)我们可以保存p 或pend 以供进一步使用,例如用于顺序写入。
在 C 风格(但已经是 C++)变体中,这看起来像:
unsigned char * pack_int(unsigned char *p, unsigned value)
{
unsigned char *pend = p + sizeof(value);
while(p != pend)
{
// ~ is a bit-wise not, ~0 produces an int with all bits set
*p = value & ((unsigned char)~0);
value >>= CHAR_BIT;
p++;
}
return p;
}
使用 ((unsigned char)~0) 而不是 0xFF 文字只是为了防止不是 8 位的字节。编译器会将其转换为正确的文字值。
C++ 允许此实现与类型无关。例如。仍然需要顺序迭代器来处理输出位置的一种:
template <class InIt, class T>
InIt pack(InIt p, T value)
{
using target_t = std::make_unsigned_t<std::remove_reference_t<decltype(*p)>>;
using src_t = std::make_unsigned_t<T>;
InIt pend = p + sizeof(T);
src_t val = static_cast<src_t> (value); // if T is signed, it would fit anyway.
while(p != pend)
{
*p = (val & (target_t)~0);
val >>= CHAR_BIT;
p++;
}
return pend;
}
在 C++17 中可以使用
InIt pend = p;
std::advance(pend, sizeof(T));
在 C++ 中更好的实现是在编译期间通过应用递归模板来静态生成转换序列,而不是使用循环。
这是一个功能齐全的程序,它同时使用了这两种功能:
#include <iostream>
#include <array>
#include <climits>
#include <type_traits>
// C-styled function
// packs value into buffer, returns pointer to the byte after its end.
unsigned char * pack_int(unsigned char *p, unsigned value)
{
unsigned char *pend = p + sizeof(value);
while(p != pend)
{
// ~ is a bit-wise not, ~0 produces an int with all bits set
*p = value & ((unsigned char)~0);
value >>= CHAR_BIT;
p++;
}
return p;
}
// a type-agnostic template
template <class InIt, class T>
InIt pack(InIt p, T value)
{
using target_t = std::make_unsigned_t<std::remove_reference_t<decltype(*p)>>;
using src_t = std::make_unsigned_t<T>;
InIt pend = p + sizeof(T);
src_t val = static_cast<src_t> (value); // if T is signed, it would fit anyway.
while(p != pend)
{
*p = (val & (target_t)~0);
val >>= CHAR_BIT;
p++;
}
return pend;
}
int main(int argc, char** argv )
{
std::array<unsigned char, 16> buffer = {};
auto ptr = pack_int(&(buffer[0]), 0xA4B3C2D1);
ptr = pack(ptr, (long long)0xA4B3C2D1);
pack(ptr, 0xA4B3C2D1);
std::cout << std::hex;
for( auto c : buffer)
std::cout << +c << ", ";
std::cout << "{end}\n";
}
这个输出是
d1, c2, b3, a4, d1, c2, b3, a4, 0, 0, 0, 0, d1, c2, b3, a4, {end}
序列d1, c2, b3, a4,重复两次,显然是十六进制值0xA4B3C2D1 的反向表示。在与内存中unsigned int 的表示匹配的小端系统上。对于 3500(十六进制 0xDAC),它将是 ac, d, 0, 0。
在通信中,“网络顺序”被接受为标准,也称为“大端”,其中最重要的字节在前,这需要对上述算法稍作改动。