【问题标题】:Unusual behaviour with 18446744073709551615ull18446744073709551615ull 的异常行为
【发布时间】:2021-02-21 11:18:36
【问题描述】:
unsigned long long m = 18446744073709551615ull;
unsigned long long i;

for (i=18446744073709551610ull; i<m; ++i) {
            printf("%llu \n", i);
    }

这会产生预期的结果: 18446744073709551610, 18446744073709551611, 18446744073709551612, 18446744073709551613, 18446744073709551614。

for (i=18446744073709551610ull; i<m+1; ++i) {
                    printf("%llu \n", i);
                }

这根本不打印任何内容。

for (i=18446744073709551610ull; i<=m; ++i) {
                    printf("%llu \n", i);
                }

这会打印无穷无尽的 6 位数字,每次运行程序时都从不同的数字开始,直到程序被取消。 我一直在打印 C 中所有可用的 Pell 数字,但希望程序在达到 C 中所有可用的 Pell 数字时停止。但是它会打印数百个六位数字,然后打印“分段错误(核心转储)”。这意味着在 C 中使用最大数时有四种不同的结果。这是 Pell 数代码:

unsigned long long x = 1, y = 2, z;
unsigned long long pell_series[52];

                        pell_series[0] = 0;
                        pell_series[1] = 1;
                        
                        printf("%d,",0);
                        printf("%d,",1);
                        printf("%d,",2);
            
                        for (int q=2; q<m; q++) {
                        z = x + (y*2);
                        pell_series[q] = z;
                        printf("%llu,", z);
                        x = y;
                        y = z;
                        }

我知道 C 在使用最大数字时可能是不可预测的,但即使是不可预测性也是不可预测的。我的问题是 - 有没有办法让程序在达到最大可用数量时停止?

更新: 感谢您的所有回复,他们真的很有见地。我刚刚注意到我的 Pell 数代码中有一个巨大的错误:当然,代码应该只在 z 小于 m 时运行,而不是 q

【问题讨论】:

  • “我知道 C 在使用最大数字时可能是不可预测的” 无论是什么疯狂的方式将那些毫无价值的垃圾吸入你的知识库,别再听了。在您的平台上,ULONG_LONG_MAX18446744073709551615 任何试图在 +1 处设计增量或表达式的任何尝试都将导致溢出(这是无符号的定义行为,但这对您并没有真正的帮助)。与其将基于超出限制的循环作为断点,不如考虑一个循环,其 tail 逻辑在实际达到限制时为 break
  • 你为什么不打印m呢?还是m+1
  • 无符号数非常容易预测。
  • @IanStewart 仅次于 asm,C 和使用它的人一样“安全”,而且绝对不安全。允许你上吊是过分好战的,如果你试图上吊,它会毫不犹豫。 IE。它和你一样好。精通它,你就能做出惊人的事情。不这样做,你可以非常非常容易地非常做一些可怕的事情。

标签: c max


【解决方案1】:

i&lt;m+1 案例

18446744073709551615ull 是您的平台上unsigned long long 可以表示的最大值(64 位)。所以m + 1 溢出,这(对于无符号整数)是定义的行为:它换行为0。因此i&lt;m+1 变成i&lt;0,这永远不会是真的,所以循环甚至不会运行一次。

i&lt;=m 案例

这会打印无穷无尽的 6 位数字,每次运行程序时都从不同的数字开始,直到程序被取消。

这不是我所看到的,也不是按照标准应该发生的事情。你对此很确定吗?这是应该发生的事情......

在某个时刻i 达到最大值18446744073709551615ull,等于m,因此进入循环。然后++i 递增i,导致它溢出并回绕到0。显然i&lt;=m 仍然为真,i 等于零,所以再次进入循环体并从零开始计数,无穷无尽

所有这些都是完美定义的行为,没有什么不可预测的(unsigned long long 的大小除外,它是实现定义的)。

如果您希望循环运行到并包括最大可能值,您可以将i &gt; 0 作为循环条件。或者,如果您希望更明确一点,请将条件留空并将if (i == m) { break; } 放在循环体的末尾。

【讨论】:

    【解决方案2】:
    1. 不要使用幻数。您在 limits.h 头文件中定义了方便的宏

    现在回答您的问题。第一个循环正常执行。 第二个循环:控制变量环绕并且永远不会停止。 ULLONG_MAX + 1 == 0

    #include <stdio.h>
    #include <limits.h>
    
    int main(void)
    {
        unsigned long long m = ULLONG_MAX;
        unsigned long long i;
    
        for (i = ULLONG_MAX - 5; i<m; ++i) {
            printf("%llu \n", i);
        }
    
        printf("\nWraparound will happen now\n\n");
    
        for (i=ULLONG_MAX - 5; i<=m; ++i) {
            printf("%llu \n", i);
        }
    }
    

    https://godbolt.org/z/d9j1qv

    如果打印m & m+1 的值,为什么m+1 的大小写不起作用很容易测试

    int main(void)
    {
        unsigned long long m = ULLONG_MAX;
        unsigned long long i;
    
        for (i=ULLONG_MAX - 5; i<m+1; ++i) {
            printf("%llu \n", i);
        }
    
        printf("m = %llu, m+1 == %llu\n", m, m+1);
    }
    
    x86-64 gcc 10.2
    
    Program returned: 0
    Program stdout
    
    m = 18446744073709551615, m+1 == 0
    

    如您所见,for 循环中的条件永远不会满足

    https://godbolt.org/z/3d4nxT

    【讨论】:

      【解决方案3】:

      一切皆有可能。:)

      这个循环

      for (i=18446744073709551610ull; i<m; ++i) {
              printf("%llu \n", i);
      }
      

      为了清楚起见,可以改写为

      for (i = ULLONG_MAX - 5; i < ULLONG_MAX; ++i) {
              printf("%llu \n", i);
      } 
      

      所以正好输出5个数字。

      在这个循环中

      for (i=18446744073709551610ull; i<m+1; ++i) {
               printf("%llu \n", i);
      }
      

      可能会被重写

      for (i = ULLONG_MAX - 5; i < ULLONG_MAX + 1; ++i) {
              printf("%llu \n", i);
      } 
      

      表达式ULLONG_MAX + 1 中存在整数溢出,对于无符号整数产生0。所以实际上你有

      for (i = ULLONG_MAX - 5; i < 0; ++i) {
              printf("%llu \n", i);
      } 
      

      所以两者都不会输出。

      在这个循环中

      for (i=18446744073709551610ull; i<=m; ++i) {
                printf("%llu \n", i);
      }
      

      可能会被重写

      for (i = ULLONG_MAX - 5; i <= ULLONG_MAX; ++i) {
              printf("%llu \n", i);
      } 
      

      还有另一个溢出。此时表达式++i 溢出。在i 等于ULLONG_MAX 之后,i 的下一个值将是0 和循环的条件

      i <= ULLONG_MAX 
      

      再次计算为逻辑true。所以你有一个无限循环。

      当达到最大值时停止循环的一种简单方法是使用 do-while 循环而不是 for 循环。

      unsigned long long int i = ULLONG_MAX - 5;
      
      do
      {
          printf("%llu \n", i);
      } while ( i++ != ULLONG_MAX );
      

      这是一个演示程序。

      #include <stdio.h>
      #include <limits.h>
      
      int main(void) 
      {
          unsigned long long int i = ULLONG_MAX - 5;
          
          do
          {
              printf( "%llu\n", i );
          } while ( i++ != ULLONG_MAX );      
          
          return 0;
      }
      

      程序输出是

      18446744073709551610
      18446744073709551611
      18446744073709551612
      18446744073709551613
      18446744073709551614
      18446744073709551615
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-02-19
        • 2018-03-14
        • 2020-05-02
        • 2015-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多