要提出替代解决方案,复制位和避免 UB 的最佳方法是通过memcpy
:
template<typename INT_T>
INT_T read_big_endian(uint8_t const *data) {
std::make_unsigned_t<INT_T> tmp = 0;
for (size_t i = 0; i < sizeof(INT_T); i++) {
tmp <<= 8;
tmp |= *data;
data++;
}
INT_T result;
memcpy(&result, &tmp, sizeof(tmp));
return result;
}
有了这个,您将不会通过将无符号类型转换为有符号类型来获得 UB,并且通过优化,它可以编译为与您的示例完全相同的程序集。
#include <cstdint>
#include <cstring>
#include <type_traits>
template<typename INT_T>
INT_T read_big_endian(uint8_t const *data) {
std::make_unsigned_t<INT_T> tmp = 0;
for (std::size_t i = 0; i < sizeof(INT_T); i++) {
tmp <<= 8;
tmp |= *data;
data++;
}
return static_cast<INT_T>(tmp);
}
template<typename INT_T>
INT_T read_big_endian2(uint8_t const *data) {
std::make_unsigned_t<INT_T> tmp = 0;
for (std::size_t i = 0; i < sizeof(INT_T); i++) {
tmp <<= 8;
tmp |= *data;
data++;
}
INT_T res;
memcpy(&res, &tmp, sizeof(res));
return res;
}
// Just to manifest the template expansions.
auto read32_1(uint8_t const *data) {
return read_big_endian<int32_t>(data);
}
auto read32_2(uint8_t const *data) {
return read_big_endian2<int32_t>(data);
}
auto read64_1(uint8_t const *data) {
return read_big_endian<int64_t>(data);
}
auto read64_2(uint8_t const *data) {
return read_big_endian2<int64_t>(data);
}
编译clang++ /tmp/test.cpp -std=c++17 -c -O3
为:
_Z8read32_1PKh: # read32_1
movl (%rdi), %eax
bswapl %eax
retq
_Z8read32_2PKh: # read32_2
movl (%rdi), %eax
bswapl %eax
retq
_Z8read64_1PKh: # read64_1
movzbl (%rdi), %eax
shlq $8, %rax
movzbl 1(%rdi), %ecx
orq %rax, %rcx
shlq $8, %rcx
movzbl 2(%rdi), %eax
orq %rcx, %rax
shlq $8, %rax
movzbl 3(%rdi), %ecx
orq %rax, %rcx
shlq $8, %rcx
movzbl 4(%rdi), %eax
orq %rcx, %rax
shlq $8, %rax
movzbl 5(%rdi), %ecx
orq %rax, %rcx
shlq $8, %rcx
movzbl 6(%rdi), %edx
orq %rcx, %rdx
shlq $8, %rdx
movzbl 7(%rdi), %eax
orq %rdx, %rax
retq
_Z8read64_2PKh: # read64_2
movzbl (%rdi), %eax
shlq $8, %rax
movzbl 1(%rdi), %ecx
orq %rax, %rcx
shlq $8, %rcx
movzbl 2(%rdi), %eax
orq %rcx, %rax
shlq $8, %rax
movzbl 3(%rdi), %ecx
orq %rax, %rcx
shlq $8, %rcx
movzbl 4(%rdi), %eax
orq %rcx, %rax
shlq $8, %rax
movzbl 5(%rdi), %ecx
orq %rax, %rcx
shlq $8, %rcx
movzbl 6(%rdi), %edx
orq %rcx, %rdx
shlq $8, %rdx
movzbl 7(%rdi), %eax
orq %rdx, %rax
retq
在 x86_64-linux-gnu 上使用clang++ v8
.
大多数情况下,memcpy
优化将编译为与您想要的完全相同的程序集,但没有 UB 的额外好处。
更新正确性:OP 正确地指出这仍然是无效的,因为有符号的 int 表示不需要是二进制补码(至少在 C++20 之前),这将是实现定义的行为。
AFAICT,直到 C++20,实际上似乎没有一种简洁的 C++ 方式来对 int 执行位级操作,而实际上不知道有符号 int 的位表示,这是实现定义的。话虽如此,只要您知道您的编译器会将 C++ 整数类型表示为二进制补码,那么在 OP 的第二个示例中usingmemcpy
或 the都应该有效。static_cast
C++20 专门将有符号整数表示为二进制补码的部分主要原因是因为大多数现有编译器已经将它们表示为二进制补码。GCC和LLVM(以及 Clang)都已经在内部使用了二进制补码。
这似乎不是完全可移植的(如果这不是最佳答案,这是可以理解的),但我想你知道你将使用什么编译器来构建你的代码,所以你可以在技术上包装这个或你的第二个例子检查您是否使用了适当的编译器。