【问题标题】:Do unsigned integers get promoted to signed? What is the type of expression (uint16_t)-1 * (uint16_t)-1无符号整数会升级为有符号吗?表达式的类型是什么 (uint16_t)-1 * (uint16_t)-1
【发布时间】:2020-10-30 10:58:39
【问题描述】:

uint32_tuint64_t 的结果是预期的,但 uint8_tuint16_t 的促销活动很奇怪。在 c++14/c++17、gcc 和 clang、64 位 linux 上测试,sizeof(int) == 4

#include <cstdint>    
using namespace std;

static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // OK, but why not 1?
static_assert( uint8_t(-1)*uint8_t(-1) == 1);       // error: static assertion failed
static_assert( uint16_t(-1)*uint16_t(-1) == 1);     // error: static_assert expression is not an integral constant expression
static_assert( uint32_t(-1)*uint32_t(-1) == 1);     // OK
static_assert( uint64_t(-1)*uint64_t(-1) == 1);     // OK

以下情况下std::uint16_t是否提升为int?

static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

编译器消息是:

error: static_assert expression is not an integral constant expression
static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

note: value 4294836225 is outside the range of representable values of type 'int'
static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

更有趣的是,std::uint8_t 的相同断言是正确的,但 失败

static_assert( uint8_t(-1)*uint8_t(-1) == uint8_t(uint8_t(-1)*uint8_t(-1)));  //error: static_assert failed
static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // does not fail

所以看起来uint8_t 被提升为uint16_t,但uint16_t 被提升为已签名 intuint32_t 未被提升 uint64_t

任何人都可以告诉我为什么要进行这些促销,是在标准中指定的,还是在不同的实现中可能有所不同?

【问题讨论】:

  • -1 转换为任何无符号整数类型会给出该类型可以表示的最大值。无法以无符号类型表示值 -1

标签: c++ unsigned integer-promotion


【解决方案1】:

看起来uint8_t 被提升为uint16_t,但uint16_t 被提升为有符号整数。 uint32_t 未被提升 uint64_t

不,这是不正确的。只有比 int 窄的类型才会被提升为 int,因此如果 int 超过 16 位,那么 uint8_tuint16_t 都会被提升为 int。

uint8_t(-1)*uint8_t(-1)的情况下,相当于0xFF*0xFF,结果为0xFE01

OTOH uint16_t(-1)*uint16_t(-1)0xFFFF*0xFFFF 相同,导致 0xFFFE0001 = 4294836225 并且超出 int 的范围。并且有符号溢出是未定义的行为

【讨论】:

  • 那么为什么 uint8_t(-1)*uint8_t(-1) == 0xfe01,而不是 0xfffffe01(32 位)?
  • @user2622016 uint8_t(-1) 是 0xFF,0xFF*0xFF 完全在 int 的范围内。那里没有发生溢出。 uint8_t(0xFF) 零扩展为 int(0xFF) 而不是符号扩展,因为它是无符号类型
  • 你知道如何抑制提升为有符号整数,并将其提升为无符号整数吗?我希望 uint16_t 乘法的结果为无符号类型,因此在每个架构上都没有 UB,即使 sizeof(int) == 2 或 sizeof(int)==8。
  • 好的,我明白了。我需要在两者之间显式转换为无符号以抑制转换为有符号整数: uint16_t(unsigned(uint16_t(-1))*unsigned(uint16_t(-1))) == 1
  • Do you know how to suppress the promotion to signed int 使用unsigned。不是无符号类型,而是unsigned(uint16_t(-1))。 IE。将值显式转换为等级大于或等于int 的类型。 static_assert(uint16_t(unsigned(uint16_t(-1))*unsigned(uint16_t(-1))) == 1);
【解决方案2】:

无符号整数会升级为有符号整数吗?

这取决于操作数相对于int 的大小。但是,是的,这是典型的。

static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // OK, but why not 1?

它不是 1,因为 uint8_t(-1) 是 255 而 255*255 是 65025。在具有 32 位 int 的系统上,这在可表示的值范围内。

所以看起来 uint8_t 被提升为 uint16_t

没有。促销总是发生在int 或更高级别的整数上。在int 是 16 位的系统上,uint8_t 可以提升为 16 位类型,但由于 uint8_t 的所有值都可以由带符号的 int 表示,这将是提升的结果。

在不同的实现上会有所不同吗?

不同语言实现的可表示值范围不同,这会影响提升的类型。

是否在标准中指定

是的。

你知道如何抑制提升为有符号整数,并将其提升为无符号整数吗?

不,无法更改促销活动。但是您可以将值显式转换为所需的类型,这样就不会进行提升。

【讨论】:

  • 谢谢!我选择了:uint16_t(unsigned(uint16_t(0xFFFF))*unsigned(uint16_t(0x8001)))。没有UB,结果是uint16_t。
【解决方案3】:

算术运算符不接受小于int 的类型作为参数。
对于较小的参数,将应用积分提升到 int

这是结果不一致的原因:

  • 当乘以小于int 的类型时,您将获得晋升,
  • 在将较大的类型相乘时(如果它们相同)。

确切的规则太复杂了,无法在不出错的情况下进行总结,但在那些 CppReference 文章中有描述:

【讨论】:

    猜你喜欢
    • 2022-01-20
    • 2023-03-20
    • 2020-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多