在C#中,for(;;)和while(true)有什么区别?

17

从语法上看,它们都会无限循环,直到遇到 break 语句,但它们编译成相同的东西吗?for 循环是否稍微快一些,因为它没有要检查的条件?除了代码可读性外,有什么区别吗?


请参见https://dev59.com/OnE95IYBdhLWcg3wf98F。 - Bertrand Marron
这是一个好问题。我觉得很有趣的是编译器把示例代码优化成了一个“do while”循环。 - Wil P
1
for(;;)是A)由前C编程人员编写的,B)比较短四个字符 :) - hobbs
7个回答

36

考虑到这个输入:

private static void ForLoop()
{
    int n = 0;
    for (; ; )
    {
        Console.WriteLine(n++);
    }
}

private static void WhileLoop()
{
    int n = 0;
    while (true)
    {
        Console.WriteLine(n++);
    }
}

...您将获得此输出:

.method private hidebysig static void  ForLoop() cil managed
{
  // Code size       14 (0xe)
  .maxstack  3
  .locals init ([0] int32 n)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  dup
  IL_0004:  ldc.i4.1
  IL_0005:  add
  IL_0006:  stloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_000c:  br.s       IL_0002
} // end of method Program::ForLoop


.method private hidebysig static void  WhileLoop() cil managed
{
  // Code size       14 (0xe)
  .maxstack  3
  .locals init ([0] int32 n)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  ldloc.0
  IL_0003:  dup
  IL_0004:  ldc.i4.1
  IL_0005:  add
  IL_0006:  stloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_000c:  br.s       IL_0002
} // end of method Program::WhileLoop

非常相似,我会说(甚至可以说是完全一样)。


除非我漏掉了什么,否则它们是相同的,除了注释和标识符名称。 - T.E.D.
@T.E.D. 是的,它们是相同的。 - Fredrik Mörk
18
嗯,第5行有点不同......哦,等一下,那是我显示器上的一个污点 ;) - Mike Caron
8
我们真的要争论无限循环的相对速度吗? - Steven Sudit
2
@Steven:我当然希望不是这样。我们只是在满足好奇心,至少我是这样的。 - Fredrik Mörk
显示剩余4条评论

21
在现代编译器中,两者无任何区别。
然而从历史上看,“for(;;)”被实现为单个跳转,而“while(true)”则会检查真值。
我更喜欢“while(true)”,因为它更清楚地表明我的意图。

2
+1 绝对没错。while(true)for(;;) 更清晰明了。我到底在等什么呢? - Matthew Jones
1
我非常赞同你应该选择你认为更清晰的那个,尽管我可能不同意哪一个更清晰... - T.E.D.
2
为什么不使用 loop: ... goto loop - Bertrand Marron
5
哦,天哪!不能使用“goto”!如果我们使用它,世界就会灭亡! - Mike Caron
6
如果我们使用“goto”语句,我认为世界不会灭亡,但程序可能会突然跳转到宇宙中的一个新位置。 - Dr. Wily's Apprentice
显示剩余6条评论

2

我没有检查过输出的代码,但是不应该有任何差别。任何一个像样的编译器都会进行足够简单的循环优化,以确保条件是一个常量表达式,因此不需要每次迭代都进行检查。

如果其中一个比另一个快,那么C#编译器的作者需要解释清楚...


1
如果我可以的话,我建议您考虑一个稍微不同的问题。如果您经常使用其中任何一个,那么您可能正在编写糟糕的代码结构。虽然有一些像嵌入式系统这样的东西确实可以永远运行,但在大多数正常代码中,循环并不会永远运行。编写一个声称永远运行的循环通常意味着您已经将循环的退出条件隐藏在某个其他控制流程(例如,if (whatever) break;)中,并将其作为循环的真正退出。

这可以而且通常应该避免。虽然有些情况下break语句是有意义的,但它们通常应该用于处理异常情况,而不是编写一个说一件事但做另一件事的循环(即,说“永远运行”,但实际上是“运行直到满足条件”)。


@supercat:我同意,但这并不能证明什么。你应该使用while (file.getline())而不是for (;;) if (!file.getline()) break; - Jerry Coffin
@supercat:是的,通常将测试放在循环的中间最清晰,并在失败时跳出循环。 - Steven Sudit
@Jerry:这是一个可行的例子,但并不是唯一的工作方式。哨兵也可以是一个特定的值,比如“0”。 - Steven Sudit
我曾经写过一个 RFC 822 头部解析器,它会循环直到遇到一个空行,但在这个循环内部,它还必须向前查看以确定下一行是否是一个续行,并在内部保持循环,直到将整个内容连接起来。在所有这些操作之后,它还必须检查键/值对是否在语法和语义上都有效,如果无效则退出。我想这可能可以通过混淆来避免 while(true),但为什么要费这个劲呢?这是一个真正的多重退出条件的情况,无法在顶部的单个位置确定。 - Steven Sudit
1
@Jerry Coffin:那个while()循环的条件超出了我对一个表达式具有多少副作用才被认为是荒谬的阈值。为什么还要使用循环体呢?为什么不使用"while ((((x=next_value()) != sentinel) && (process(x),1));"呢? - supercat
显示剩余7条评论

0

调试汇编编译成while(true)。使用反射器,您可以查看结果。

    static void Main(string[] args)
    {
        ExecuteWhile();

        ExecuteFor();
    }

    private static void ExecuteFor()
    {
        for (; ; )
        {
            Console.WriteLine("for");
            string val = Console.ReadLine();
            if (string.IsNullOrEmpty(val))
            {
                Console.WriteLine("Exit for.");
                break;
            }
        }
    }

    private static void ExecuteWhile()
    {
        while (true)
        {
            Console.WriteLine("while");
            string val = Console.ReadLine();
            if (string.IsNullOrEmpty(val))
            {
                Console.WriteLine("Exit while.");
                break;
            }
        }
    }

在Reflector中检查ExecuteFor方法。

private static void ExecuteFor()
{
    while (true)
    {
        Console.WriteLine("for");
        if (string.IsNullOrEmpty(Console.ReadLine()))
        {
            Console.WriteLine("Exit for.");
            return;
        }
    }
}

同一代码的优化版本在ExecuteFor上产生不同的结果

private static void ExecuteFor()
{
    do
    {
        Console.WriteLine("for");
    }
    while (!string.IsNullOrEmpty(Console.ReadLine()));
    Console.WriteLine("Exit for.");
}

为了更加详细,这里是经过优化的ExecuteWhile代码...

private static void ExecuteWhile()
{
    do
    {
        Console.WriteLine("while");
    }
    while (!string.IsNullOrEmpty(Console.ReadLine()));
    Console.WriteLine("Exit while.");
}

它真的会编译成 while(true),还是反编译器会这样反编译它? :p - Mark H
如果您查看从相同程序集创建的IL,则会以这种方式读取。 - Wil P

0

它们编译成相同的东西。您可以编写一个测试应用程序来实现两种方法,然后使用ILDASM确认它们的IL代码是相同的。


3
你可以直接查看Fredrik的回答,省去麻烦。 :) - DarLom

0

正如其他人所提到的,使用任何现代编译器都不应该有任何区别。

我在我的电脑上进行了简短的测试,计时了一些使用其中之一的操作,它们始终需要几乎相同的时间。当然,由于其他进程正在运行,这些测试并不是100%准确的,但如果速度有差异(不应该有),那么它是微不足道的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接