将 uint32_t 转换为 int32_t 而不会出现溢出或过于复杂的风险

Convert a uint32_t to an int32_t without either a risk of overflow or excessive complexity

在 C++ 中,如何最好地将 uint32_t 快速转换为 int32_t

一些尝试:

uint32_t y = UINT32_MAX;
int32_t x = (int32_t)y; // UB on overflow
int32_t x = *(int32_t*)&y; // does this violate strict aliasing?
union { 
  int32_t i; 
  uint32_t u;
} u;
u.i = ...;
printf("%" PRIu32 "\n", u.u);

这和 memcpy(&uint_var, &int_var, sizeof uint_var) 是在不调用未定义行为的情况下进行此类转换的两种标准方法。

另请参阅:

  • Is type-punning through a union unspecified in C99, and has it become specified in C11?
#include <assert.h>
#include <limits.h>
#include <stdlib.h>

int makesigned(unsigned x) {
  if (x <= (unsigned) INT_MAX) {
    return (int) x;
  }

  /* assume 2's complement */
  if (x >= (unsigned) INT_MIN) {
    return 0 - (int)(-x);
  }

  abort();
  return 0;
}

int main(void) {
  assert(makesigned(0) == 0);
  assert(makesigned(INT_MAX) == INT_MAX);
  assert(makesigned(UINT_MAX) == -1);
  assert(makesigned(INT_MIN) == INT_MIN);
  return 0;
}

int32_t x = (int32_t)y; 不是溢出也不是 UB。 溢出 是指算术运算产生的结果超出可表示值的范围。但是,转换不是算术运算。

这种情况是实现定义的行为。我所知道的所有实现都将行为定义为不改变表示。

请注意,此处不需要转换。你可以写int32_t x = y;。 实际上,这更简单并且总是有效。如此多的代码都依赖于此,以至于没有供应商会定义任何其他行为(并不是说他们有任何理由这样做)。


int32_t x = *(int32_t*)&y 不是 UB。它不违反严格的别名,因为允许类型的签名版本作为未签名版本的别名。此代码保证生成与对应的 uint32_t 具有相同表示的 int32_t(即 "wrapping",因为这些类型保证是 2 的补码)。