无穷循环

无穷循环（endless loop）又称无穷回圈、无限循环（infinite loop），是指程式的控制流程一直在重复执行某一段程式码，无法结束的情形，其原因可能是因为程式中的循环没有设结束循环条件，或是结束循环的条件不可能成立等。在协作式多工（cooperative multitasking）的操作系统中，无穷循环会使系统没有反应，若是先占式多工（preemptive multitasking）的系统中，无穷循环会用掉所有可用的处理器时间，不过可以由用户结束程序。

有些无穷循环是因为程式错误而意外产生的，不过也有些程式的架构是以无穷循环为基础，在循环执行过程中处理各种事件，这种无穷循环是正常的，不是程式错误。

无穷循环是造成系统假死机的原因之一，其他的可能原因包括死锁或是内存区段错误。

忙碌等待循环是在外界特定条件时（例如有按键输入）才会离开的循环，有时忙碌等待循环也被称为是无穷循环，但此情形和上述的不太一样。忙碌等待循环可以借由外界事件而结束循环，但上述的无穷循环是无法结束的。

## 蓄意或无意产生的无穷循环

循环是指程式可能会重复执行的某一组指令，若重复执行，在特定条件成立后才会结束，若因为循环的特性，造成特定条件无法满足，就会形成无穷循环。

### 蓄意产生的无穷循环

有些情形下程式会蓄意产生无穷循环。例如早期使用ROM匣的电子游戏机，在主程式的循环是无穷循环，没有结束条件，原因是一般主程式若不执行时，系统控制权是交由操作系统，但这类电子游戏机没有操作系统，因此让主程式为无穷循环，程式会一直执行到断电为止。

现在许多的电脑互动系统需要定期的监控用户的输入或是设备的活动，因此当系统闲置时，仍然会在无穷循环中执行，直到系统被重置或断电为止。像在阿波罗计划导航用的电脑中，程式的最外层是无穷循环，若完全没有其他工作要进行时，电脑会关闭“电脑运作中”的指示灯号。

### 无意产生的无穷循环

若程式中的无穷循环是无意产生的，也就是程序错误。新手程序员常常会出现这种错误，但在资深程序员身上也有可能发生，而且错误的原因可能相当微妙，因此不容易除错。

<image>

例如程序员想要从收集链表中所有的资料，因此会依链表依序前进，直到链表的尾端为止，但若程式未考虑链表可能是循环链表，在循环链表中依序前进的程序，反而会移动到链表较前面的部分，此时就会变成无穷循环。

大部分的无穷循环可以借由详细的的代码检视而找出，不过没有通用的方式可以判断程式是否会结束，或者会永远执行（也就是无穷循环）。判断程式会结束或会永远执行的问题称为停机问题，而英国电脑科学家艾伦·图灵证明了没有判断停机问题(halting problem)的通用算法。

## 举例

以下是一些无穷循环的例子。

C语言的无穷循环：

```c
#include <stdio.h>
main()
{
  while(1) {
    printf("Infinite Loop\n");//顯示"Infinite Loop"字串
  }
}
```

上述程式会一直显示"Infinite Loop"字串。

BASIC语言的无穷循环：

```freebasic
10 PRINT "Infinite Loop"
20 GOTO 10'跳到行號=10的位置
```

X86组合语言的例子：

```asm
loop:
  ;空的無窮迴圈，直接跳到"loop"標籤的位置
  jmp loop
```

Python的例子：

```python
while True:
    print("Infinite Loop")#顯示"Infinite Loop"字串
```

### 逻辑错误

以下是一个Visual Basic无穷循环的例子：

```vb
dim x as integer
do until x > 5 '根本不會有x>5的情形
  x = 1
  x = x + 1
loop
```

每一次执行循环时x会先设定为1，然后变为2，因为数值未大于5，所以永远不会结束。若将`x = 1`由循环内部移到循环之前即可以改善此一情形。

有些程序员可能因为不熟悉特定程式语言的语法而造成无穷循环，例如以下是一段C语言的程式：

```c
#include <stdio.h>

main()
{
  int a = 0;

  while (a < 10) {
    printf("%d\n", a);
    if (a = 5) {//a設定為5，進入無窮迴圈
      printf("a equals 5!\n");
    }
    a++;
  }
  return 0;
}
```

其预期输出是数字0至9，其中5和6中间会显示"a equals 5!"，但程序员在编写程式时将设定用的`=`运算子及判断相同的`==`运算子弄混了，因此程式会在每次执行循环时都会将a设定为5，因此变数a永远无法到达10，此循环就变成了无穷循环。

现代化的编译器，例如clang，都已经可以检测到这一类的潜在问题并给出警告。

### 变数处理错误

有时不适当的循环结束条件也可能会造成无预期的无穷循环，例如以下C语言的例子：

```c
float x = 0.1;
while (x != 1.1) {//可能會因為浮點運算的誤差而出現問題
  printf("x = %f\n", x);
  x = x + 0.1;
}
```

在有些操作系统中，上述程式会执行10次循环然后结束，但有些系统中，上述程式却可能会一直执行，无法结束，问题主要在循环的结束条件(x != 1.1)要在二个浮点数相等时才结束，结果会依系统处理浮点数的方式而定，只要系统执行10次循环后的结果和1.1差一点点，上述程式就会变成无穷循环。

若将结束条件改为(x < 1.1)就没有这个问题，程式可能会多执行一次循环，但不会变成无穷循环。另一种解决方式则是用一个整数变数作为循环变数，再依此变数判断是否要结束循环。

在数值分析程式中也可能会出现无预期的无穷循环，例如程式需一直迭代到误差小于某特定值为止，但若因为运算中的舍去误差，使得误差一直无法小于该特定值，就会产生无穷循环。

### 奥尔德森循环

奥尔德森循环（Alderson loop）是指一个循环有设定结束条件，但因为程式的写法（多半是编程错误），造成永远无法满足结束条件，在针对使用者界面程式侦错时最容易出现这类的问题。

以下C的虚拟码中有一个奥尔德森循环，程式是要计算使用者输入一串数字的和，使用者输入0时结束循环，但程式中用了不正确的运算符：

```C
sum = 0;
while (true) {
    printf("Input a number to add to the sum or 0 to quit");
    i = getUserInput();
    if (i * 0) {     // 若i乘0为真，则使sum加上i的值
        sum += i;    // 但这不可能发生，因为不论i为何值（i * 0）都是0。如果条件中用的是!=而非*，代码就能正常运行
    }
    if (sum > 100) {
        break;       // 终止循环。结束条件存在，但从来没有达到过，因为sum永远不会增加
    }
}
```

“奥尔德森循环”来自一个Microsoft Access的程序员，他编写的程式产生一个有模式的对话框，使用者需要回应，程式才能继续运作，但对话框没有OK键或取消键，因此只要此对话窗出现，Access程式就无法继续运作。

### 无穷递回

无穷递回是一种由递归造成的无穷循环。例如以下计算阶乘的C语言程式：

```c
unsigned int fac(unsigned int a)
{//n!=n*(n-1)!
  return( fac(a-1) * a);
}
```

一般递回的程式会有一特定条件，此条件成立时直接计算结果，而不是透过递回来计算结果，若程式中未定义此条件，就会出现无穷递回。

无穷递回会造成堆叠溢位，而无穷递回不会结束，因此也是无穷循环的一种。不过若递回程式是使用尾端递回的处理方式，在有些程式语言（如Scheme）中会优化成循环，因此不会造成堆叠溢位。

上述的程式可以修改成没有无穷递回的程式。

```c
unsigned int fac(unsigned int a)
{
  if (a == 0) {//定義0!=1
    return 1;
  }
  else {
    return( fac(a-1) * a);  
  }
}
```

## 多个模组产生的无穷循环

无穷循环也可能因为多个模组之间的互动而产生。考虑一台服务器若收到无法理解的需求时，会回应错误讯息，此架构中不会有无穷循环。但若有二台上述的服务器（A和B），互相交换资料，A收到由B所送出无法理解的需求，会回应错误讯息给B，但若B也无法理解A送出的需求（其实是A的错误讯息），会再以自己的格式回应错误讯息给A，A收到后无法理解，会再回应错误讯息给B……。

## 假无穷循环

假无穷循环是指一个循环看似不会结束，但只是一个执行很长时间，最后仍会结束的循环。

以下是一个C语言for循环的程式：

```c
unsigned int i;
for (i = 1; i != 0; i++) {
  /* loop code */
}
```

上述程式每次执行时都将i加1，若i等于0时才会结束循环，此程式看似不会结束，但最后还是会结束。程式中型态为`unsigned int`的变数，其数值有一定上限，当数值已到上限，再加1时，变数数值就会变为0，因此让程式结束。实际的上限值依系统及编译器而不同，假如`unsigned int`是一个16个位元的字元组，上述的循环会执行65536次。若使用高精度计算，程式会一直执行到内存无法储存`i`为止。