运行-C++ 中的位复制(位掩码)
Run-time bit copy (bit-masking) in C++
我手头有一个问题并以一种方式解决了它,但我对我如何解决它并不满意,因为它并不适用于所有情况。解决方案必须在 C++(11) 中。
我有一个字符数组和一个整数。给定相对于数据的位偏移量和长度(以位为单位)。我想从数组中提取从 offset 到 offset+length 的位并将它们存储在 out.
char8_t data[8];
int32_t out;
int32_t offset;
int32_t length;
Figure 与 offset=24; length=4;
偏移量和长度仅在 运行 时可用。因此,我想避免创建位掩码。我个人通过将完整数组转换为 int64_t 然后右移 (64-offset-length) 和左移 (64-length).
来解决它
out = (*(int64_t*)data) >> (64-offset-length) << (64-length);
问题:如果我的数组更长,就没有原语来捕获完整的数组。我的解决方案不再有效。有没有更好的(缩放)方法来做到这一点?
在理想的世界中,我可以创建一个带有位偏移的指针,但这是 C++ 不是一个理想的世界。
备选方案 我想过:通过遍历数组和左移,在“out”上用 += 添加位。 很不优雅!
我知道那里有类似的问题,但要么没有得到很好的回答,要么答案对性能有很大影响。
首先,您的方法将取决于字节顺序,即取决于系统是将最重要的字节存储在相应 8 字节内存块的开头还是结尾。
其次,我会使用无符号数据类型,例如uchar8_t data[8]
和 uint32_t
以便正确处理位移位和(自动)类型提升。
如果您确切知道 data[8]
特定信息的存储位置以及存储顺序,您可以按如下方式编写:
uint32_t out = data[0] + 256*data[1];
...
因此,您的 "decoder" 将被收紧到原始数据的顺序/含义;您的 data
可能比最大整数数据类型更长;并且您避免了将有符号整数值移到有符号位上可能引入的未定义行为。
如果您的偏移量不是 8 的倍数,即 "value" 不是从一个字节的开头开始,您仍然可以使用移位操作来纠正此问题。假设该值从 2 位的偏移量开始;然后你可以写:
uint32_t out = (data[0] >> 2) + (data[1] << 6) + (data[2] << (6+8))
但是 - 最后 - 您的目标将被限制在特定数量的位上,因为您特定平台上的 C 语言将保证每种原始数据类型的特定大小,unsigned long long
可能仍然是 64 位。这个限制是实现定义的,标准保证每种数据类型的最小位。这个限制是来自寄存器还是其他东西,你无法知道 - 它的实现已定义。
你试过std::vector<bool>
了吗?
它是 std::vector
的特化,结合了矢量的动态大小和 std::bitset
.
的紧凑性
我会临时使用 bitset。先循环复制字节对齐,再进行位对齐
unsigned startbit = offset;
unsigned startbyte = startbit / 8;
unsigned endbit = offset + length - 1;
unsigned endbyte = endbit / 8;
bitset<8*(sizeof(out) + 1)> align(0);
for(unsigned byte = endbyte; byte >= startbyte; --byte) { // byte align copy
// for(unsigned byte = startbyte; byte <= endbyte; ++byte) { // check endianess
align <<= 8;
align |= data[byte];
}
align >>= startbit % 8; // bit align
align &= ((1 << length) - 1); // mask
out = align.to_ullong();
我使用std::bitset
和boost::dynamic_bitset
来表示二进制数据并对其进行操作。如果长度固定,std::bitset
效果很好,否则 boost::dynamic_bitset
是一个不错的选择。有了这个,您可以使用重载位运算符提取位:
#include <boost/dynamic_bitset.hpp>
using boost::dynamic_bitset;
dynamic_bitset<unsigned char> extract(unsigned char* first, unsigned char* last, int offset, int length) {
dynamic_bitset<unsigned char> bits(first, last);
bits >>= bits.size() - (offset + length);
bits.resize(length);
return bits;
}
因此,您可以使用 dynamic_bitset<>
而不是 int32_t out;
来有效地保存任意位长度的值。
我手头有一个问题并以一种方式解决了它,但我对我如何解决它并不满意,因为它并不适用于所有情况。解决方案必须在 C++(11) 中。
我有一个字符数组和一个整数。给定相对于数据的位偏移量和长度(以位为单位)。我想从数组中提取从 offset 到 offset+length 的位并将它们存储在 out.
char8_t data[8];
int32_t out;
int32_t offset;
int32_t length;
Figure 与 offset=24; length=4;
偏移量和长度仅在 运行 时可用。因此,我想避免创建位掩码。我个人通过将完整数组转换为 int64_t 然后右移 (64-offset-length) 和左移 (64-length).
来解决它out = (*(int64_t*)data) >> (64-offset-length) << (64-length);
问题:如果我的数组更长,就没有原语来捕获完整的数组。我的解决方案不再有效。有没有更好的(缩放)方法来做到这一点?
在理想的世界中,我可以创建一个带有位偏移的指针,但这是 C++ 不是一个理想的世界。
备选方案 我想过:通过遍历数组和左移,在“out”上用 += 添加位。 很不优雅!
我知道那里有类似的问题,但要么没有得到很好的回答,要么答案对性能有很大影响。
首先,您的方法将取决于字节顺序,即取决于系统是将最重要的字节存储在相应 8 字节内存块的开头还是结尾。
其次,我会使用无符号数据类型,例如uchar8_t data[8]
和 uint32_t
以便正确处理位移位和(自动)类型提升。
如果您确切知道 data[8]
特定信息的存储位置以及存储顺序,您可以按如下方式编写:
uint32_t out = data[0] + 256*data[1];
...
因此,您的 "decoder" 将被收紧到原始数据的顺序/含义;您的 data
可能比最大整数数据类型更长;并且您避免了将有符号整数值移到有符号位上可能引入的未定义行为。
如果您的偏移量不是 8 的倍数,即 "value" 不是从一个字节的开头开始,您仍然可以使用移位操作来纠正此问题。假设该值从 2 位的偏移量开始;然后你可以写:
uint32_t out = (data[0] >> 2) + (data[1] << 6) + (data[2] << (6+8))
但是 - 最后 - 您的目标将被限制在特定数量的位上,因为您特定平台上的 C 语言将保证每种原始数据类型的特定大小,unsigned long long
可能仍然是 64 位。这个限制是实现定义的,标准保证每种数据类型的最小位。这个限制是来自寄存器还是其他东西,你无法知道 - 它的实现已定义。
你试过std::vector<bool>
了吗?
它是 std::vector
的特化,结合了矢量的动态大小和 std::bitset
.
我会临时使用 bitset。先循环复制字节对齐,再进行位对齐
unsigned startbit = offset;
unsigned startbyte = startbit / 8;
unsigned endbit = offset + length - 1;
unsigned endbyte = endbit / 8;
bitset<8*(sizeof(out) + 1)> align(0);
for(unsigned byte = endbyte; byte >= startbyte; --byte) { // byte align copy
// for(unsigned byte = startbyte; byte <= endbyte; ++byte) { // check endianess
align <<= 8;
align |= data[byte];
}
align >>= startbit % 8; // bit align
align &= ((1 << length) - 1); // mask
out = align.to_ullong();
我使用std::bitset
和boost::dynamic_bitset
来表示二进制数据并对其进行操作。如果长度固定,std::bitset
效果很好,否则 boost::dynamic_bitset
是一个不错的选择。有了这个,您可以使用重载位运算符提取位:
#include <boost/dynamic_bitset.hpp>
using boost::dynamic_bitset;
dynamic_bitset<unsigned char> extract(unsigned char* first, unsigned char* last, int offset, int length) {
dynamic_bitset<unsigned char> bits(first, last);
bits >>= bits.size() - (offset + length);
bits.resize(length);
return bits;
}
因此,您可以使用 dynamic_bitset<>
而不是 int32_t out;
来有效地保存任意位长度的值。