【问题标题】:Is (uint64_t)-1 guaranteed to yield 0xffffffffffffffff?(uint64_t)-1 是否保证产生 0xffffffffffffffff?
【发布时间】:2013-08-18 21:08:13
【问题描述】:

我知道,C 标准很好地定义了(unsigned)-1 必须产生 2^n-1,即。 e.一个无符号整数,其所有位都已设置。 (uint64_t)-1ll 也是如此。但是,我在 C11 标准中找不到指定如何解释 (uint64_t)-1 的内容。

那么,问题是:C 标准中是否有任何保证,以下哪项成立?

(uint64_t)-1 == (uint64_t)(unsigned)-1   //0x00000000ffffffff
(uint64_t)-1 == (uint64_t)(int64_t)-1    //0xffffffffffffffff

【问题讨论】:

  • 你的最终目标到底是什么?获取无符号整数可表示的最大值?
  • 不,只是对这种特殊情况下的标准保证感到好奇。我可以想到其他方法来产生最大整数值:-)

标签: c++ c standards c11


【解决方案1】:

是的。请参阅 C11 6.3.1.3 有符号和无符号整数:

1当一个整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新的类型表示,则不变。

2 否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。60)

3 否则,新类型是有符号的,值不能在其中表示;结果要么是实现定义的,要么引发实现定义的信号。

60) 规则描述的是数学值的算术运算,而不是给定类型表达式的值。

情况 2 适用,因此 -1 以 0x10000000000000000 为模减少为 0xffffffffffffffff。

【讨论】:

  • 我认为您的评论适用于问题中的第二个表达式;那一定是真的。它不适用于第一个表达式的 RHS;如果sizeof(unsigned) == 8(unsigned)-1 为0xFFFF_FFFF_FFFF_FFFF,如果sizeof(unsigned) == 4 则为0xFFFF_FFFF,并且该值可在uint64_t 中表示。因此,标准不保证第一个表达式的计算结果为 true。
  • 我正在回答主题行中的问题。我没有提到(uint64_t)(unsigned)-1 的问题,这显然是不同的,除非unsigned 恰好是64 位的。
【解决方案2】:

表达式1-1 的类型为int。转换为uint64_t时,适用加减2n直到值在范围内的原则,所以结果总是2n-1,在这个n = 64的情况。因此,(uint64_t)-1 始终为 264-1..

表达式(int64_t)-1 的计算结果为-1,因此同样的推理适用于表达式(uint64_t)(int64_t)-1,它也总是计算为264-1。

另一方面,(unsigned)-1unsigned int 类型的正值,可能是 216-1, 232-1, 264-1 或其他各种值,具体取决于编译平台。这些值在转换为 uint64_t 时可能不会产生 264-1。

【讨论】:

  • @user2485710 你不应该问 OP 吗?是的,使用UINT64_MAX 似乎不太容易出错,但这不是问题。
  • 该问题适用于同时转换为 a) 较大类型和 b) 无符号类型的任何有符号整数。如(unsigned)(signed char)-123
  • @cmaster:你的例子是两个连续的演员表——没有两个“同时”演员表这样的东西。只有一个类型转换为更大且(无)符号的类型,定义明确,或者有一系列特定顺序的转换,定义同样明确(但可能不同)
  • @PascalCuoq: (uint64_t)-1U(uint64_t)(-((unsigned)1)) 相同,因为常量字面量从不包含 C(或 C++)中的符号,浮点常量的指数部分除外。所以- 将是常量上的一元前缀操作。
  • @cmaster 顺便说一句,您询问了 C 标准,但您将问题标记为“C++”并使用g++ 进行测试? C 和 C++ 是不同的语言。
【解决方案3】:

我猜你写的是(uint64_t)-1 而不是-1ULL,因为你不想假设unsigned long long 的大小?如果是这样,那很好。但是,还有一个尚未提及的替代方案(实际上并没有回答您的问题),但可以通过回避问题来省去很多担忧:

另一种选择

一个好习惯是始终使用UINT64_C(x) 而不是(uint64_t)x。这是在<stdint.h> 中定义的宏,它会根据需要自动附加UULULL。因此,UINT64_C(-1) 解析为 -1U-1UL-1ULL,具体取决于您的目标。这保证始终正常工作。

类型转换的危险

请注意,(uint64_t)x 实际上通常甚至无法正常工作。例如,

(uint64_t)2147483648                // RISKY

在某些编译器上生成警告,因为值 2147483648 (2^31) 太大而无法放入 32 位整数,并且以下内容甚至无法远程工作:

(uint64_t)1000000000000000000       // RISKY

但是,如果您改用UINT64_C(),那么一切都是黄金:

UINT64_C(2147483648)                // GOOD

UINT64_C(1000000000000000000)       // GOOD

UINT64_C(-1)                        // GOOD

注意事项:

  • _C 后缀代表“常数”。
  • <stdint.h> 中,还有用于有符号和无符号值的 8 位、16 位和 32 位版本。
  • 对于 –1 的特殊情况,您也可以只写UINT64_MAX

【讨论】:

  • 好吧,你的猜测是错误的;这个问题实际上是关于 C/C++ 标准如何定义强制转换以在单个操作中改变整数的符号和大小。尽管如此,UINT64_C() 宏是一个有趣的旁注,尽管它确实没有回答问题。顺便说一句:long long int 保证至少为 64 位,因此(uint64_t)2147483648ull 保证产生正确的结果。
  • 虽然最好记住 -1u 不是 -1 转换为无符号,而是运算符-应用于 1u,因此其他规则适用于使其工作:brnz.org/hbr/?p=1433
  • IIRC,C++ 整数文字将有一个足够宽的类型来表示值。因此,如果 int 是 32 位类型,则 2147483648 将具有类型 longlong long。它没有实际上是有风险的。 (除非我记错了,或者存在 C/C++ 差异)。但是,1024*1024*1024*1024 是有风险的,因为它会在最后的乘法中溢出一个 32 位的 int
  • UINT64_C(-1) 未定义(无需诊断);根据 C11 7.20.4/2,参数必须是一个无后缀的整数常量,其值在该类型的范围内。 -1 不是常数,它是一元减号表达式。 -UINT64_C(1) 将是一个正确的选择
  • 标准要求您标记为RISKY 的两行(如果uint64_t 当然存在)
【解决方案4】:

这是一个可以用几行代码回答的问题。

#include <stdio.h>

main()
{
    int          x;
    unsigned int y;

    x = -1;
    printf("\n0x%08x", x);

    y = (unsigned int) -1;
    printf("\n0x%08x", y);

}

在 Eclipse/Microsoft C 编译器上运行此代码会产生:

0xffffffff
0xffffffff

一个类似的程序可以告诉你uint64_t 产生了什么。

最后,如果您了解计算机如何使用 2 的补码来添加数字,那么您就会明白,对于任意位数(8、32、64 等)的字,-1 始终是所有 ff 的每个字节字/双字等

【讨论】:

  • 这不是“试看”就能回答的问题。
  • 作为记录,标准不保证有符号整数类型的 2 补码,也不保证转换为有符号类型会保留最低有效位。这些都是实现定义的。 2 的补码表示和通过消除最高有效位进行的转换是常见的实现选择。
  • 我们使用的计算机什么时候开始不使用 2 的补码进行算术运算了?您对语言规范的阅读过多。
  • 我认为这个问题是关于“C 标准中的保证”,但我可能读得太多了。
  • @R.. — 嗯。如果结果是肯定的(没有歧义),那么“测试并查看”肯定无助于证明这一点。但是只需要一个来自单个编译器的否定结果来证明(uint64)-1不能保证产生0xFFFFFFFFFFFFFFFF...所以理论上它值得一试,机会不大它失败了。这当然与标准是否规定它是否应该起作用是完全不同的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-03
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 2016-10-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多