unsigned long 的确切取值范围是多少?

What is the exact value range for unsigned long?

我正在完成书中的练习 "Learn C the hard way"。练习 7 要求 reader 找到使 unsigned long 超出范围的值。

Change long to unsigned long and try to find the number that makes it too big.

所以我的方法是首先在我的机器上获取unsigned long的大小:

printf("SIZEOF ULONG: %lu", sizeof(unsigned long));

这将打印 8 作为结果。所以假设 unsigned long 将在我的机器上占用 64 位,我在 Wikipedia.

上查找了最大范围

64 位(字、双字、长字、long long、quad、quadword、qword、int64)

我原以为用上面的值声明一个 unsigned long 会在没有警告的情况下编译,直到我将该值递增 1。但是结果不同。编译以下程序会导致警告。

#include <stdio.h>
int main()
{
    unsigned long value = 18446744073709551615;
    printf("SIZEOF ULONG: %lu", sizeof(unsigned long));
    printf("VALUE: %lu", value);
    return 0;
}

bla.c: In function ‘main’:
bla.c:5:27: warning: integer constant is so large that it is unsigned
     unsigned long value = 18446744073709551615;
                           ^~~~~~~~~~~~~~~~~~~~

那么为什么 gcc 抱怨这个值太大了,我以为我已经把它声明为 unsigned

use unsigned long value = 18446744073709551615ul; else 18446744073709551615ul 被读取为 int 而不是 long

如果您想知道 unsigned long 中的位数和最大值:

#include <stdio.h>
#include <limits.h>

int main()
{
    printf("number of bits in ULONG: %d\nULONG_MAX = %lu\n",
       sizeof(unsigned long) * CHAR_BIT,
       ULONG_MAX);
    return 0;
}

编译与执行:

pi@raspberrypi:/tmp $ gcc -pedantic -Wextra u.c
pi@raspberrypi:/tmp $ ./a.out
number of bits in ULONG: 32
ULONG_MAX = 4294967295

是的,我的 long 不在 64 位上

如果十进制整数常量适合该范围,则其类型为 int,否则它们的类型为 longlong long。它们没有无符号类型,如果该值超出这些有符号范围,您会收到警告。您需要为常量添加 ul 后缀以具有正确的类型。

还有一种更简单的方法可以在不知道其大小的情况下获取该类型的最大值。只需将 -1 转换为这种类型即可。

unsigned long value = (unsigned long)-1;

编译器分几步处理 unsigned long value = 18446744073709551615;。在它可以用一个值初始化 value 之前,它必须从源代码中读取 18446744073709551615 并解释它。

源代码中的数字18446744073709551615是独立存在的——它不会立即受到影响,稍后将用于初始化value。按照C标准中的规则进行处理。

这些规则表示带小数位且没有后缀的数字是 intlong intlong long int,以第一个可以表示该值的为准.由于 18446744073709551615 太大了,它不适合这些类型。

编译器警告您,由于 18446744073709551615 不适合任何这些类型,因此它正在使用无符号类型。在其他情况下,这可能会改变代码的含义。但是,在这种情况下,由于该值会立即用于初始化 unsigned long,因此会产生预期的效果。

要解决此问题,您可以添加 u 后缀,将其更改为 18446744073709551615u。对于以 u 为后缀的十进制数字,C 标准表示该类型是 unsigned intunsigned long intunsigned long long int 中第一个可以表示该值的类型。

(C 标准继续说,如果一个值对于列出的类型来说太大,C 实现可以用扩展整数类型表示它或者它没有类型。没有类型的后果探索起来可能很有趣,但这是语言律师问题的主题。)

您需要一个整数字面值的后缀,而不适合 long int(或 long long int,自 C99 和 C++11 起)。 unsigned long int 符合以下任何条件:

unsigned long value = 18446744073709551615u;
unsigned long value = 18446744073709551615lu;
unsigned long value = 18446744073709551615ul;

请在此处查看后缀table:

https://en.cppreference.com/w/c/language/integer_constant(对于 C) https://en.cppreference.com/w/cpp/language/integer_literal(对于 C++)

整数常量 18446744073709551615 太大,无法在您的系统上表示为 intlong intlong long int。编译器警告您它使它成为 unsigned long long.

让我们尝试编译这个程序:

#include <stdio.h>

int main() {
    if (-1 < 18446744073709551615)
        printf("TRUE\n");
    else
        printf("FALSE\n");

    return 0;
}

gcc 发出警告:

#1 with x86-64 gcc 8.2
<source>: In function 'main':
<source>:7:14: warning: integer constant is so large that it is unsigned
     if (-1 < 18446744073709551615)
              ^~~~~~~~~~~~~~~~~~~~

程序输出是这样的:

TRUE

clang 生成更明确的警告:

#1 with x86-64 clang 7.0.0
<source>:7:14: warning: integer literal is too large to be represented in a signed integer type, interpreting as unsigned [-Wimplicitly-unsigned-literal]
    if (-1 < 18446744073709551615)
             ^

但是程序输出是:

FALSE

如果 18446744073709551615 确实被解释为无符号,就好像写成 18446744073709551615u0xffffffffffffffff,比较必须使用无符号算术执行并且失败,因为两个数字具有相同的值。 clang 按照它说的做,但 gcc 没有。

在您的情况下,将值存储到 unsigned long 变量中应该会产生预期的结果,但是向常量添加 u 后缀将确保它被编译器解析为正确的类型。

但是请注意,您可以通过将 -1 强制转换为该类型来获得任何无符号类型的最大值。您还可以使用 <limits.h> 中定义的宏:类型 unsigned long 的最大值为 ULONG_MAX.