在没有类型双关的情况下切换字节顺序
Switching byte order without type punning
我需要切换字节的顺序,使内容为 (byte1, byte2) -> (byte2, byte1) 的 int16。我使用联合来完成此操作:
union ConversionUnion
{
uint8_t m_8[4];
uint16_t m_16[2];
uint32_t m_32;
};
//use
uint16_t example = 0xFFDE
ConversionUnion converter;
converter.m_16[0] = example;
std::swap(converter.m_8[0], converter.m_8[1]);
example = converter.m_16[0]; //0xDEFF
现在这确实适用于 gcc,但我被告知这是未定义的行为(gcc 6.3,C++11)。
问题:
1) 这真的是未定义的行为吗,我问,因为我以前在嵌入式代码中见过这个。其他 Whosebug 问题似乎在争论这个问题,实际上谁是正确的(对于 C++11 和 C++14)。
2) 如果这是未定义的行为,是否可以在不以可移植方式进行一堆位移的情况下完成字节顺序交换。我真的很讨厌移位,它丑得要命。
人们在某种程度上不同意这一点。我认为
这是未定义的行为
但是有我的意见并不是一个有价值的补充。
无符号类型的字节交换很容易(顺便说一句,字节交换有符号类型没有意义)。只需提取单个字节并重新排列。将丑陋隐藏在 constexpr
函数或宏中。
constexpr uint16_t bswap(uint16_t value);
{
uint16_t high_byte = (value >> 8) & 0xff;
uint16_t low_byte = value & 0xff;
return (low_byte << 8) | high_byte;
}
顺便说一句,如果您在嵌入式代码中看到某些东西并且它可以工作,这并不表示它是安全的!嵌入式代码通常会为了效率而牺牲可移植性,有时会使用未定义的行为,这是说服特定编译器生成高效代码的唯一方法。
通过 char*
允许类型双关,那么为什么不使用它而不是联合?
uint16_t example = 0xFFDE;
char *char_alias = reinterpret_cast<char*>(&example);
std::swap(char_alias[0], char_alias[1]);
依赖语言的未定义行为或晦涩语义(联合)不一定更地道或更易于阅读。我发现这个循环更容易解析:
uint32_t example = 0xc001c0de;
unsigned char *p = reinterpret_cast<unsigned char*>(&example);
for (size_t low = 0, high = sizeof(example) - 1;
high > low;
++low, --high)
{
std::swap(p[low], p[high]);
}
我需要切换字节的顺序,使内容为 (byte1, byte2) -> (byte2, byte1) 的 int16。我使用联合来完成此操作:
union ConversionUnion
{
uint8_t m_8[4];
uint16_t m_16[2];
uint32_t m_32;
};
//use
uint16_t example = 0xFFDE
ConversionUnion converter;
converter.m_16[0] = example;
std::swap(converter.m_8[0], converter.m_8[1]);
example = converter.m_16[0]; //0xDEFF
现在这确实适用于 gcc,但我被告知这是未定义的行为(gcc 6.3,C++11)。
问题:
1) 这真的是未定义的行为吗,我问,因为我以前在嵌入式代码中见过这个。其他 Whosebug 问题似乎在争论这个问题,实际上谁是正确的(对于 C++11 和 C++14)。
2) 如果这是未定义的行为,是否可以在不以可移植方式进行一堆位移的情况下完成字节顺序交换。我真的很讨厌移位,它丑得要命。
人们在某种程度上不同意这一点。我认为
这是未定义的行为
但是有我的意见并不是一个有价值的补充。
无符号类型的字节交换很容易(顺便说一句,字节交换有符号类型没有意义)。只需提取单个字节并重新排列。将丑陋隐藏在
constexpr
函数或宏中。constexpr uint16_t bswap(uint16_t value); { uint16_t high_byte = (value >> 8) & 0xff; uint16_t low_byte = value & 0xff; return (low_byte << 8) | high_byte; }
顺便说一句,如果您在嵌入式代码中看到某些东西并且它可以工作,这并不表示它是安全的!嵌入式代码通常会为了效率而牺牲可移植性,有时会使用未定义的行为,这是说服特定编译器生成高效代码的唯一方法。
通过 char*
允许类型双关,那么为什么不使用它而不是联合?
uint16_t example = 0xFFDE;
char *char_alias = reinterpret_cast<char*>(&example);
std::swap(char_alias[0], char_alias[1]);
依赖语言的未定义行为或晦涩语义(联合)不一定更地道或更易于阅读。我发现这个循环更容易解析:
uint32_t example = 0xc001c0de;
unsigned char *p = reinterpret_cast<unsigned char*>(&example);
for (size_t low = 0, high = sizeof(example) - 1;
high > low;
++low, --high)
{
std::swap(p[low], p[high]);
}