每当人们询问与编程有关的停机问题时,人们都会回答“如果你只添加一个循环,你就会得到停机程序,因此你不能自动化任务”
说得通。如果您的程序有一个无限循环,那么当您的程序运行时,您无法知道程序是否仍在处理输入,或者它是否只是无限循环。
但其中一些似乎违反直觉。如果我正在编写一个以源代码作为输入的停机问题解决程序会怎样。rascher@localhost$ ./haltingSolver source.c
如果我的代码(source.c)如下所示:
for (;;) { /* infinite loop */ }
我的程序似乎很容易看到这一点。“看看循环,看看条件。如果条件只是基于文字,没有变量,那么你总是知道循环的结果。如果有变量(例如 while (x < 10)),看看是否这些变量永远不会被修改。如果没有,那么你总是知道循环的结果。
当然,这些检查不会是微不足道的(计算指针算术等),但似乎并非不可能。例如:
int x = 0
while (x < 10) {}
可以被检测到。连同 - 尽管不是微不足道的:
int x = 0
while (x < 10)
{
x++;
if (x == 10)
{
x = 0
}
}
现在用户输入呢?这就是踢球者,这就是使程序不可预测的原因。
int x = 0;
while (x < 10)
{
scanf("%d", &x); /* ignoring infinite scanf loop oddities */
}
现在我的程序可以说:“如果用户输入 10 或更大,程序将停止。在所有其他输入时,它将再次循环。”
这意味着,即使有数百个输入,也应该能够列出程序将停止的条件。确实,当我编写程序时,我总是确保有人能够终止它!我并不是说生成的条件列表很容易创建,但对我来说似乎并非不可能。您可以从用户那里获取输入,使用它们来计算指针索引等 - 但这只会增加条件数量以确保程序将终止,并不会导致无法枚举它们。
那么究竟什么是停机问题呢?关于我们不能编写问题来检测无限循环的想法,我有什么不明白的?或者,为什么“循环”是一个经常被引用的例子?
更新
所以,让我稍微改变一下这个问题:什么是适用于计算机的停机问题?然后我会回应一些评论:
很多人都说过,程序必须能够处理“任意输入”。但是在计算机中,从来没有任何任意输入。如果我只输入一个字节的数据,那么我只有 2^8 个可能的输入。所以,举个例子:
int c = getchar()
switch (c) {
case 'q':
/* quit the program */
}
突然间,我刚刚考虑了所有的可能性。如果c
位模式为 0x71,它会做一件事。对于所有其他模式,它会做其他事情。即使是接受任意字符串输入的程序也永远不会真正“任意”,因为资源是有限的,这意味着虽然“任意”理论适用……但它与实践并不完全是一对一的。
人们引用的另一个例子是:
while (n != 1)
if (n & 1 == 1)
n = 3 * n + 1;
else
n /= 2;
如果 n 是一个 32 位整数......那么我可以直观地告诉你这是否会停止。
我想这个编辑没有问任何问题,但我见过的最有说服力的例子是这个:
假设您有您的神奇程序/方法来确定程序停止。
public bool DeterminesHalt(string filename, string[] args){
//runs whatever program you tell it do, passing any args
//returns true if the program halts, false if it doesn't
}
现在假设我们编写了一小段代码,例如...
public static void Main(string[] args){
string filename = Console.ReadLine(); //read in file to run from user
if(DeterminesHalt(filename, args))
for(;;);
else
return;
}
所以对于这个例子,我们可以编写一个程序来做与我们神奇的停止方法完全相反的事情。如果我们以某种方式确定给定程序将停止,我们就会跳入无限循环;否则,如果我们确定程序处于无限循环中,则结束程序。
再说一次,如果你故意编写一个包含无限循环的程序......“解决停机问题”有点没有意义,不是吗?