分享一个C++的按位拷贝的代码。
由于标准库中所有的拷贝都是以字节为单位的,而没有按位进行拷贝的。最近碰到了一个需要按位操作数据的情况,写了一段代码。如有错误,望请指摘。
#include
#include
class BitOperator
{
public:
/**
* @brief: 按位拷贝。如从{0x00, 0xff, 0xff, 0xff}中的第2位开始,拷贝12位到{0x00, 0x00, 0x00, 0x00}
* 的第5位开始。则得到:{0x00, 0xf8, 0x01, 0x00}。
*
* 低地址 高地址
* ↓ ↓
* src: 0000 0000 1111 1111 1111 1111 1111 1111 => {0x00, 0xff, 0xff, 0xff}
* -- ---- ---- --
* ↘
* ___ ____ ____ _
* dst: 0000 0000 0000 0000 0000 0000 0000 0000 => {0x00, 0x00, 0x00, 0x00}
*
* res: 0000 0000 0001 1111 1000 0000 0000 0000 => {0x00, 0xf8, 0x01, 0x00}
*
* @param dst_bitmap 目标地址
* @param src_bitmap 源地址
* @param bit_length 要拷贝的位数
* @param dst_start_bit 起始位在目标字节中的index。该起始位会被包括在拷贝范围内。该值不应该超过7。
* @param src_start_bit 起始位在源地址指向的字节中的index。该起始位的位数据会被覆盖。该值不应该超过7。
*/
static inline void bitcpy(
void* dst_bitmap,
const void* src_bitmap,
uint32_t bit_length,
uint8_t dst_start_bit = 0,
uint8_t src_start_bit = 0)
{
// 不需要错位拷贝,则起始和终止字节额外处理,中间使用memcpy
if (dst_start_bit == src_start_bit)
{
return __bitcpy(
reinterpret_cast<uint8_t*>(dst_bitmap),
reinterpret_cast<const uint8_t*>(src_bitmap),
bit_length,
src_start_bit);
}
// 需要错位拷贝。
// 需要源数据右移后拷贝至目的地址。
else if (dst_start_bit < src_start_bit)
{
uint8_t diff = src_start_bit - dst_start_bit;
__bitcpy<RIGHT_SHIFT>(
reinterpret_cast<uint32_t*>(dst_bitmap),
reinterpret_cast<const uint32_t*>(src_bitmap),
bit_length,
diff,
src_start_bit);
}
// 需要源数据左移后拷贝至目的地址。
else
{
uint8_t diff = dst_start_bit - src_start_bit;
__bitcpy<LEFT_SHIFT>(
reinterpret_cast<uint32_t*>(dst_bitmap),
reinterpret_cast<const uint32_t*>(src_bitmap),
bit_length,
diff,
src_start_bit);
}
}
/**
* @brief: 位拷贝的安全版本,会对输入参数进行检测。如果参数正确则返回true。否则返回false。
*
* @param dst_bitmap 目标地址
* @param src_bitmap 源地址
* @param bit_length 要拷贝的位数
* @param dst_start_bit 起始位在目标字节中的index。该起始位会被包括在拷贝范围内。该值不应该超过7。
* @param src_start_bit 起始位在源地址指向的字节中的index。该起始位的位数据会被覆盖。该值不应该超过7。
* @return true 正确拷贝
* @return false 输入参数有问题
*/
static bool bitcpy_s(
void* dst_bitmap,
const void* src_bitmap,
uint32_t bit_length,
uint8_t dst_start_bit = 0,
uint8_t src_start_bit = 0)
{
// 输入检测
if (dst_bitmap == nullptr)
return false;
if (src_bitmap == nullptr)
return false;
if (dst_start_bit > 7)
return false;
if (src_start_bit > 7)
return false;
if (bit_length == 0)
return true;
bitcpy(dst_bitmap, src_bitmap, bit_length, dst_start_bit, src_start_bit);
return true;
}
private:
// 定义左移为true, 右移为false。
constexpr static bool LEFT_SHIFT = true;
constexpr static bool RIGHT_SHIFT = false;
private:
/**
* @brief: 计算选中从start位开始,bit_length长度的位的掩码。将数据与该掩码相与即可选中指定位。
* 比如从第0位开始,选中8位,则掩码为:0x0000 0000 0000 00FF。
*
* @param start 起始位
* @param bit_length 位长度
* @return uint64_t 掩码
*/
inline static uint64_t calculate_mask(uint8_t start, uint8_t bit_length)
{
uint64_t res = 1ULL << start;
return (res << bit_length) - res;
}
/**
* @brief: 计算源数据和目标数据起始位相同的情况。此时不需要错位复制。起始字节和终止字节单独处理,中间使
* 用memcpy即可。
*
* @param dst_ptr 目标地址
* @param src_ptr 源地址
* @param bit_length 要拷贝的位数
* @param start_bit 起始位
*/
static void __bitcpy(
uint8_t* dst_ptr,
const uint8_t* src_ptr,
uint32_t bit_length,
uint8_t start_bit)
{
// 如果要拷贝的数据在一个字节中,则直接选中拷贝。
if (start_bit + bit_length <= 8)
{
auto mask = calculate_mask(start_bit, bit_length);
*dst_ptr = (*src_ptr & mask) | (*dst_ptr & (~mask));
return;
}
//如果起始位不为0,则第一个字节需要单独处理。
if (start_bit != 0)
{
uint8_t mask = (1U << start_bit) - 1;
*dst_ptr = (*src_ptr & mask) | (*dst_ptr & (~mask));
bit_length -= 8 - start_bit;
++dst_ptr;
++src_ptr;
}
//中间的数据直接使用memcpy进行拷贝
auto byte_size = bit_length >> 3;
memcpy(dst_ptr, src_ptr, byte_size);
//如果最后有不完整的字节数据需要拷贝,进行处理。
bit_length &= 7;
if (bit_length != 0)
{
dst_ptr += byte_size;
src_ptr += byte_size;
uint8_t mask = (1 << bit_length) - 1;
*dst_ptr = (*src_ptr & mask) | (*dst_ptr & (~mask));
}
}
/**
* @brief: 以4字节为一个数据块,拷贝一个数据块。将源地址起始的4字节内需要拷贝的数据,全部拷贝至目标地址
* 指向的8字节空间中。因为目标地址是按8字节进行考虑的,因此不用对源数据进行切割。
*
* @tparam _Left_Shift 拷贝时需要左移还是右移
* @param dst_bitmap 目标地址
* @param src_bitmap 源地址
* @param bit_length 要拷贝的位数
* @param start_diff 源地址和目标地址的起始位之差。
* @param src_start_bit 起始位在源地址指向的字节中的index。该起始位的位数据会被覆盖。该值不应该超过7。
*/
template <bool _Left_Shift>
inline static void copy_4_bytes(
void* dst_bitmap,
const uint32_t* src_bitmap,
uint8_t bit_length,
uint8_t start_diff,
uint8_t src_start_bit = 0)
{
// 从源地址获取数据
uint64_t temp = *src_bitmap;
uint64_t mask = calculate_mask(src_start_bit, bit_length);
temp &= mask;
// 左移或右移掩码与数据,使其与目标地址所在的位置对齐
if constexpr (_Left_Shift)
{
temp <<= start_diff;
mask <<= start_diff;
}
else
{
temp >>= start_diff;
mask >>= start_diff;
}
//取出目标地址的数据,并将源数据中选中的部分覆盖至目标数据中,最后拷贝回目标地址
uint64_t res = (*reinterpret_cast<uint64_t*>(dst_bitmap)) & (~mask);
res |= temp;
*reinterpret_cast<uint64_t*>(dst_bitmap) = res | temp;
}
/**
* @brief: 处理需要错位拷贝的情况。以4字节为一个数据块进行处理以减少循环次数。
*
* @tparam _Left_Shift 拷贝时需要左移还是右移
* @param dst_ptr 目标地址
* @param src_ptr 源地址
* @param bit_length 要拷贝的位数
* @param start_diff 源地址和目标地址的起始位之差。
* @param src_start_bit 起始位在源地址指向的字节中的index。该起始位的位数据会被覆盖。该值不应该超过7。
*/
template <bool _Left_Shift>
static void __bitcpy(
uint32_t* dst_ptr,
const uint32_t* src_ptr,
uint32_t bit_length,
uint8_t start_diff,
uint8_t src_start_bit)
{
// 如果待拷贝的数据均在一个数据块内,直接进行拷贝。
if (src_start_bit + bit_length <= 32)
{
copy_4_bytes<_Left_Shift>(dst_ptr, src_ptr, bit_length, start_diff, src_start_bit);
return;
}
// 拷贝第一个数据块。
copy_4_bytes<_Left_Shift>(
dst_ptr, src_ptr++, 32 - src_start_bit, start_diff, src_start_bit);
bit_length -= 32 - src_start_bit;
// 如果需要左移,则目标地址的第一个数据块已经被填充完毕,指向下一个数据块
if constexpr (_Left_Shift)
dst_ptr++;
// 如果需要右移,则目标地址的第一个数据块未被全部填充,计算尚未被填充的起始位。此时后面的数据拷贝又
// 退化为了需要左移
else
start_diff = 32 - start_diff;
// 拷贝中间的数据块.
while (bit_length > 32)
{
copy_4_bytes<LEFT_SHIFT>(dst_ptr++, src_ptr++, 32, start_diff);
bit_length -= 32;
}
// 拷贝最后一个数据块.
copy_4_bytes<LEFT_SHIFT>(dst_ptr, src_ptr, bit_length, start_diff);
}
};