GOTO与DO WHILE的区别

3
这两个C#代码片段的执行有所不同?
do
{
    Console.WriteLine(x.ToString());
    ++x;
} 
while (x < 7);

并且

label:
{
    Console.WriteLine(x.ToString());
    ++x;
}
if (x < 7) goto label;

我正在试图弄清楚为什么goto语句如此糟糕。 谢谢。
编辑:如果我添加括号,代码片段就非常相似。
编辑2:在Visual Studio中,我点击了“转到反汇编”,然后得到了以下代码:
            do
            {
00000037  nop 
                Console.WriteLine(x.ToString());
00000038  lea         ecx,[ebp-40h] 
0000003b  call        63129C98 
00000040  mov         dword ptr [ebp-48h],eax 
00000043  mov         ecx,dword ptr [ebp-48h] 
00000046  call        63148168 
0000004b  nop 
                ++x;
0000004c  inc         dword ptr [ebp-40h] 
            } 
0000004f  nop 
            while (x < 7);
00000050  cmp         dword ptr [ebp-40h],7 
00000054  setl        al 
00000057  movzx       eax,al 
0000005a  mov         dword ptr [ebp-44h],eax 
0000005d  cmp         dword ptr [ebp-44h],0 
00000061  jne         00000037 

并且
            label:
            {
                Console.WriteLine(x.ToString());
00000069  lea         ecx,[ebp-40h] 
0000006c  call        63129C98 
00000071  mov         dword ptr [ebp-4Ch],eax 
00000074  mov         ecx,dword ptr [ebp-4Ch] 
00000077  call        63148168 
0000007c  nop 
                ++x;
0000007d  inc         dword ptr [ebp-40h] 
            }
00000080  nop 
            if (x < 7) goto label;
00000081  cmp         dword ptr [ebp-40h],7 
00000085  setge       al 
00000088  movzx       eax,al 
0000008b  mov         dword ptr [ebp-44h],eax 
0000008e  cmp         dword ptr [ebp-44h],0 
00000092  jne         00000097 
00000094  nop 
00000095  jmp         00000068

差别在于无条件跳转。

3
goto语句会产生"意大利面条代码"。只有在一些特定情况下使用goto是被接受的,比如跳出多层嵌套循环和在switch语句中跳转。- https://en.wikipedia.org/wiki/Spaghetti_code - Bauss
2
这两个代码段的执行没有区别,但是当你需要编写比那更复杂的代码时,使用goto的代码变得难以维护。 - Candide
4
没错,底部的代码唯一的“问题”是未来的维护人员、代码审查人员或你的上司会想:“他们不知道‘while’和其他标准语言结构吗?”——“他们的代码里还有什么其他宝藏?” - Damien_The_Unbeliever
1
显然,这个问题在代码片段中是看不到的。想象一下一个大型项目,goto语句在页面之间来回跳转.. - TaW
1
使用goto可能会导致代码混乱,但这并不是绝对的。我们应该理解规则而不是盲目地重复它们:http://powerfield-software.com/?p=236 - paxdiablo
显示剩余15条评论
2个回答

9
不,我甚至认为在后台实现时,while循环就是这样实现的。使用goto的不好之处在于它鼓励你在代码中来回跳转(也被称为“意大利面条代码”:一团糟)。它使得你的代码极难阅读、调试和分析,并且引入了bug,因为你真的无法理解发生了什么。而while循环的好处在于,你和编译器都能理解它,所以可以给你良好的警告提示。

正确,但在某些情况下,“while”循环非常人为,唯一的目的是避免“goto”。 - i486
1
实际上,没有其他实现方式。在某个时刻,你必须进行跳转(除了一些特殊情况,例如repnz有自己的指令)。使用while的另一个好处是你有一个明确重复的代码块 - 它很容易看到,它隔离了局部变量,总之,它可以保护你免受许多小错误的影响。当然,它更能抵抗代码修改 - 特别是现在源代码控制普及(自动合并很痛苦),很容易意外破坏goto代码。 - Luaan
确实,@Luaan。你应该避免使用goto,只有在真的真的需要时才使用(我从来没有见过这种情况,但好吧)。 - Patrick Hofman

2
让我们来看一下IL代码:
.method private hidebysig 
    instance void While () cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 31 (0x1f)
    .maxstack 2
    .locals init (
        [0] int32 x,
        [1] bool CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    // loop start (head: IL_0003)
        IL_0003: nop
        IL_0004: ldloca.s x
        IL_0006: call instance string [mscorlib]System.Int32::ToString()
        IL_000b: call void [mscorlib]System.Console::WriteLine(string)
        IL_0010: nop
        IL_0011: ldloc.0
        IL_0012: ldc.i4.1
        IL_0013: add
        IL_0014: stloc.0
        IL_0015: nop
        IL_0016: ldloc.0
        IL_0017: ldc.i4.7
        IL_0018: clt
        IL_001a: stloc.1
        IL_001b: ldloc.1
        IL_001c: brtrue.s IL_0003
    // end loop
    IL_001e: ret
} // end of method Program::While

.method private hidebysig 
    instance void Goto () cil managed 
{
    // Method begins at RVA 0x207c
    // Code size 34 (0x22)
    .maxstack 2
    .locals init (
        [0] int32 x,
        [1] bool CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    // loop start (head: IL_0003)
        IL_0003: ldloca.s x
        IL_0005: call instance string [mscorlib]System.Int32::ToString()
        IL_000a: call void [mscorlib]System.Console::WriteLine(string)
        IL_000f: nop
        IL_0010: ldloc.0
        IL_0011: ldc.i4.1
        IL_0012: add
        IL_0013: stloc.0
        IL_0014: ldloc.0
        IL_0015: ldc.i4.7
        IL_0016: clt
        IL_0018: ldc.i4.0
        IL_0019: ceq
        IL_001b: stloc.1
        IL_001c: ldloc.1
        IL_001d: brtrue.s IL_0021

        IL_001f: br.s IL_0003
    // end loop

    IL_0021: ret
} // end of method Program::Goto

ILSpy事件标记goto语句为循环。当将其反编译为C#时,甚至将两个循环都显示为“do while”循环。
但是它们有所不同!while循环具有作用域:
        int x = 0;
        do
        {
            string z = "TEST";
            Console.WriteLine(x.ToString());
            ++x;
        }
        while (x < 7);
        Console.WriteLine(z); // invalid!

但是这是有效的:
        int x = 0;
    label:
        string z = "TEST";
        Console.WriteLine(x.ToString());
        ++x;
        if (x < 7) goto label;
        Console.WriteLine(z);

你应该使用吗?不!请参见Patrick的回答。


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