【问题标题】:Macro for endian-independent conversion to big endian字节序无关转换为大字节序的宏
【发布时间】:2019-09-17 13:52:32
【问题描述】:

是否可以在C 中编写一个宏,它采用uint32_t 并将其转换为大端表示,无论目标系统是小端还是大端,以便可以在编译时评估宏一个常数?

我发现了这个问题:endianness conversion, regardless of endianness,但是,答案只提供功能。我的情况是我希望编译时表达式能够编写如下内容:

const uint32_t magic_number = BIGENDIAN32(0x12345678);

【问题讨论】:

  • 您是否希望宏输出一个整数,该整数始终以相同的方式存储在内存中,即。 0x12 在最低地址,然后是 0x34 在地址+1,等等...?
  • 如果你需要一个幻数(所以一些没有数字意义的东西),为什么不声明一个 4 字节的数组呢?
  • @GuillaumePetitjean 是的,这是正确的。对于存储,我当然可以将其声明为数组,但我还需要通过整数比较来比较值。此外,我开始怀疑这是否可能如所描述的那样。

标签: c endianness c11


【解决方案1】:

您可以使用与字节序相关的union,以及不依赖于字节序的位移。运行时版本:

uint32_t big_endian (uint32_t n)
{
  union
  {
    uint32_t u32;
    uint8_t  u8 [4];
  } be;

  for(size_t i=0; i<4; i++)
  {
    size_t shift = (4-1-i) * 8;
    be.u8[i] = (n >> shift) & 0xFFu;
  }
  return be.u32;
}

u8[0] 在大端机器上总是包含 MS 字节。但是,n &gt;&gt; shift 将可移植地获取相关字节。值得注意的是,在大端机器上运行时,整个函数只是开销膨胀。

将其转换为丑陋的编译时宏将是这样的:

typedef union
{
  uint32_t u32;
  uint8_t  u8 [4];
} be_t;


#define BIG_ENDIAN(n) ( _Generic((n), uint32_t: (void)0), \
  (be_t){ .u8 = { ((n) >> 24)&0xFFu,                      \
                  ((n) >> 16)&0xFFu,                      \
                  ((n) >>  8)&0xFFu,                      \
                   (n)&0xFFu } }.u32)

_Generic check + , 运算符仅用于类型安全,如果卡在非标准 C 中,可以将其删除。宏使用复合文字(外部 {})形式的临时联合,初始化u8 数组(内部 {})然后返回一个 uint32_t 值。

在 little endian x86 上尝试 BIG_ENDIAN(0x12345678) 并反汇编,我得到:

mov     esi, 2018915346

2018915346 dec = 0x78563412

【讨论】:

  • (gcc -O3 for PowerPC 给了我一个废话,不知道为什么。我只能访问旧的 gcc 端口 lis 9,0x1234 + ori 9,9,22136 这似乎毫无意义 22136 =0x5678.)
  • 这是一个不错的解决方案,我可以将其构建为 C++,但如果我构建它是 C,则会收到错误“错误:初始化元素不是常量”(即使我删除_Generic 检查)。我无法真正弄清楚为什么编译器会这么说,但确实如此。
  • @nielsen 您要求在 C11 中提供 C 解决方案,这就是原因。这在 C++ 中根本行不通,因为该语言不支持联合类型双关。
  • 对,但是在使用 -std=c11 构建时,我收到了来自 gcc 的错误消息。
  • @nielsen 问题可能是您将变量放在文件范围内。我不认为联合/复合文字中间人被视为常量表达式。如果您打算将宏用作文件范围变量初始化器,则可能必须重写宏。
【解决方案2】:

考虑union复合文字

#define BIGENDIAN32(x) (((union { uint8_t  u8[4]; uint32_t u32; }) \
    { {((uint32_t)(x)>>24)&255, ((uint32_t)(x)>>16)&255, \
       ((uint32_t)(x)>> 8)&255,  (uint32_t)(x)&255} }).u32) \
//    MSbyte first                                 LSByte last

int main(void) {
  const uint32_t magic_number = BIGENDIAN32(0x12345678u);
  return (int) magic_number;
}

【讨论】:

  • @Lundin 嗯当然相似。 _Generic 的包装和be_t 的创建似乎没有必要,而且对我来说,您的答案使用了复合文字。如果您发现太多共性,请立即删除此 wiki 答案。
  • 是的,这是因为我先编写了运行时版本。 _Generic 是非常需要的,但是你不想右移签名int 否则一切都会在算术右移时中断。
  • @Lundin 没错,处理未签名的x 是有道理的。只需转换为 uint32_t 即可解决此问题。
  • 但是你创建了一个宏,它默默地接受意外的错误类型。数组、指针等
  • 与位移相比,使用联合对我来说似乎代价高昂。自己看:godbolt.org/z/WKa98esd4.
【解决方案3】:

如果您使用的是 GCC,您可以执行以下操作:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define BIGENDIAN32(_a_)    __builtin_bswap32 (_a_)
#else
#define BIGENDIAN32(_a_)    (_a_)
#endif

注意:不考虑 PDP 字节序,但你明白了

如果您希望代码可移植到其他编译器,则必须替换以下行:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

this thread 的聪明贡献者建议的宏之一,并为字节反转内置函数定义一个宏。

【讨论】:

  • 这是个好主意,尽管在链接的线程中我不确定是否有人真正完成了在编译时确定字节顺序(即使他们认为他们做到了:-))。
猜你喜欢
  • 2014-10-27
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 2014-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多