使用32位GCC进行无符号长长整数的括号初始化错误。

huangapple go评论71阅读模式
英文:

Braced Initialization Error with 32bit GCC for Unsigned Long Long

问题

When I compile the code on Godbolt with ARM32-GCC, I get the errors because the integer constant is so large that it is unsigned.

I do not get this error when using ARM64-GCC (or 32bit/64bit Clang or 32bit/64bit MSVC). But I get the error when using other 32bit GCC variants on Godbolt.

Why is this an error with these 32bit GCC compilers only?

You can fix it by using 18446744073709551615u. There is no other way to fix this error without changing the code and without disabling this warning/error in general (-Wno-narrowing).

英文:

When I compile the code

 typedef long long unsigned myuint64;
 myuint64 a[2] = {18446744073709551615, 18446744073709551615}; // error on 32bit gcc
 myuint64 b = 18446744073709551615; // no error

on Godbolt with ARM32-GCC I get the errors

(3 warnings for "integer constant is so large that it is unsigned")

error: narrowing conversion of '-1' from 'long long int' to 'myuint64' {aka 'long long unsigned int'} [-Wnarrowing]
    6 |  myuint64 a[2] = {18446744073709551615, 18446744073709551615};
      |                   ^~~~~~~~~~~~~~~~~~~~
error: narrowing conversion of '-1' from 'long long int' to 'myuint64' {aka 'long long unsigned int'} [-Wnarrowing]
    6 |  myuint64 a[2] = {18446744073709551615, 18446744073709551615}; 

(The number is the maximum value for uint64)

I do not get this error when using ARM64-GCC (or 32bit/64bit Clang or 32bit/64bit MSVC).
But I get the error when using other 32bit GCC variants on Godbolt.

Why is this an error with these 32bit GCC compilers only?

I can fix it by using 18446744073709551615u. Is there any other way to fix this error without changing the code and without disabling this warning/error in general (-Wno-narrowing)?

答案1

得分: 3

你的代码能否编译取决于是否存在__int128,它仅适用于ARM64 GCC。原因是,当你写一个没有后缀的整数字面值,比如u,编译器会从以下类型列表中选择,直到值适应其中一个类型:

  • int
  • long int
  • long long int
  • 一些实现定义的扩展有符号整数类型,如__int128

参见[lex.icon] §3

你的值18446744073709551615等于264-1,因此64位有符号整数无法容纳它,但__int128可以。

让我们看看ARM64 gcc为你的整数字面值提供了什么类型:

// 警告:整数常量太大,以致于它是无符号的
void foo(decltype(18446744073709551615)) {}
foo(__int128):
        ret

警告消息与实际发生的情况不符:你可以看到编译器输出了一个接受__int128的函数,这是你的字面值的类型。

同样的代码使用ARM gcc(32位)编译会产生:

foo(long long):
        bx lr

在这种情况下,字面值太大,无法适应任何类型,__int128不存在(尝试使用它会产生编译器错误),GCC选择将其视为警告而不是错误。

为什么你的代码只在64位编译器上工作

myuint64 a[2] = {18446744073709551615, 18446744073709551615};

GCC错误地为你的字面值选择了long long,尽管这段代码是不合法的,并将它们转换为-1myuint64是无符号的,所以它无法表示-1,而且列表初始化不允许缩小转换。结果,我们得到了编译器错误。

在64位编译器上使用__int128就没有问题,因为我们可以很好地将__int128(18446744073709551615)转换为myuint64(18446744073709551615)

结论

你发现了一种异常情况,其中编译器诊断实际上是反直觉的。忽略这些诊断并理解__int128的参与会揭示问题。

我不知道有任何编译器标志可以用来修复这个问题,所以我建议对字面值使用u后缀,这样编译器会从以下类型中选择:

  • unsigned int
  • unsigned long int
  • unsigned long long int
  • 一些实现定义的扩展无符号整数类型,例如__uint128
英文:

Whether your code compiles or not depends on whether __int128 exists, and it only exists for ARM64 GCC. The reason is that when you write an integer-literal with no suffix such as u, the compiler chooses a type from this list until the value fits in one of these types:

  • int
  • long int
  • long long int
  • some implementation-defined extended signed integer type, e.g. __int128

See [lex.icon] §3.

Your value 18446744073709551615 equals 2<sup>64</sup>-1, so a 64-bit signed integer cannot fit it, but __int128 can.

Let's reveal what type ARM64 gcc gives to your integer literal:

// warning: integer constant is so large that it is unsigned
void foo(decltype(18446744073709551615)) {}
foo(__int128):
        ret

The warning message doesn't match what really happens: You can see that the compiler outputs a function accepting __int128, which is the type of your literal.

The same code compiled with ARM gcc (32-bit) produces:

foo(long long):
        bx lr

In this case, the literal is too large to fit any of the types, __int128 doesn't exist (and attempting to use it would produce a compiler error), and GCC chooses to treat this as a warning, not an error.

Why your code only works with 64-bit compilers

myuint64 a[2] = {18446744073709551615, 18446744073709551615};

GCC falsely chooses long long for your literals even though this code is ill-formed, and converts them to -1. myuint64 is unsigned, so it cannot represent -1, and list initialization does not allow narrowing conversion. As a result, we get a compiler error.

With __int128 on a 64-bit compiler, this is not a problem, because we can convert __int128(18446744073709551615) to myuint64(18446744073709551615) just fine.

Conclusion

You have discovered an unusual situation where compiler diagnostics are really counter-intuitive. Ignoring these diagnostics and understanding that __int128 is involved reveals the problem.

I am not aware of any compiler flags that you can use to fix this, so I recommend using the u suffix for literals, in which case the compiler chooses from:

  • unsigned int
  • unsigned long int
  • unsigned long long int
  • some implementation-defined extended unsigned integer type, e.g. __uint128

huangapple
  • 本文由 发表于 2023年6月13日 01:20:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76458950.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定