将goto循环嵌套在交换机中

时间:2015-03-26 16:07:47

标签: c++ c

我想循环一个序列,但我想动态选择在序列中启动循环的位置。我设计了这种流动模式。

switch(offset){
    start:
    currentObject = objects[index++]; //a different object is chosen to be manipulated by the sequence of code
    case 0:
        sub_sequence(currentObject); // a sequence that is repeated within the larger sequence of the entire switch
        if(enough_actions) break;
    case 1:
        sub_sequence(currentObject);
        if(enough_actions) break;
    case 2:
        sub_sequence(currentObject);
        if(enough_actions) break;
    goto start;
    }

它似乎很符合我的需求,但我以前从未见过这种设计。这个设计有什么问题吗?我应该倾向于使用替代方案吗?

3 个答案:

答案 0 :(得分:4)

你构建的是 Duff的设备。虽然它避免了重复的源代码,但它不仅难以理解人类,而且同样难以为编译器进行优化。

switch(offset)
{
    case 0:
        sub_sequence(currentObject); // a sequence that is repeated within the larger sequence of the entire switch
        if(enough_actions) break;
    case 1:
        sub_sequence(currentObject);
        if(enough_actions) break;
    case 2:
        sub_sequence(currentObject);
        if(enough_actions) break;

        //a different object is chosen to be manipulated by the sequence of code
        currentObject = objects[index++];
        while(true) {
            sub_sequence(currentObject);
            if(enough_actions) break;
            sub_sequence(currentObject);
            if(enough_actions) break;
            sub_sequence(currentObject);
            if(enough_actions) break;
            currentObject = objects[index++];
        }
}

通过将循环与变量入口点分开,可以让编译器更自由地执行优化。

在原始代码中,它由start:标签和3个case:标签分隔,强制编译器分别处理两个标签之间的每个代码部分。

如果没有这些标签,编译器现在可以将特定于switch语句的优化应用于switch块,以及可能的其他循环展开或while循环的其他策略。

最后,寻找更易读的变体可能会产生更紧凑的更快的机器代码。

这可以说是少数几个可以接受“复制”代码的情况之一,因为switchwhile块只看起来相似,但仍然表现完全不同。

EDIT1:将循环移至switch语句的末尾,以便正确处理enough_actions。如果没有提前退出的条件,则循环可以放在switch块之外。

奖励:免费切换实施:

for(;!enough_actions;offset = 0,currentObject = objects[index++]) {
    for(int i = offset; i < 3 && !enough_actions; i++) {
        sub_sequence(currentObject);
    }
}

答案 1 :(得分:3)

你也可以这样做:

switch(offset)
{
    do
    {
        currentObject = objects[index++]; //a different object is chosen to be manipulated by the sequence of code
        case 0:
            sub_sequence(); // a sequence that is repeated within the larger sequence of the entire switch
            if(enough_actions) break;
        case 1:
            sub_sequence();
            if(enough_actions) break;
        case 2:
            sub_sequence();
            if(enough_actions) break;
    }
    while (1);
}

所以你要避免转到;) (如评论中所述,从技术上讲,如果需要这种行为,则无法避免goto

但是,你是对的,两者都应该符合你的需要。

答案 2 :(得分:1)

我已经检查了Microsoft编译器为下面的fibonacci函数生成的汇编代码,并且编译器仍然可以稍微修改展开的循环序列(我假设优化寄存器依赖性)。

unsigned int fib(unsigned int n)
{
unsigned int f0, f1;
    f0 = n & 1;         /* if n even, f0=0=fib(0), f1=1=fib(-1) */
    f1 = 1 - f0;        /* else       f1=0=fib(0), f0=1=fib(-1) */
    switch(n%8){
        do{
            f1 += f0;
          case 7:
            f0 += f1;
          case 6:
            f1 += f0;
          case 5:
            f0 += f1;
          case 4:
            f1 += f0;
          case 3:
            f0 += f1;
          case 2:
            f1 += f0;
          case 1:
            f0 += f1;
          case 0:
            continue;
        }while(0 <= (int)(n -= 8));
    }
    return f0;
}

制作汇编代码:

_fib    PROC                    ; _n$ = eax
        push    esi
        mov     esi, eax
        and     eax, 1
        mov     edx, esi
        mov     ecx, 1
        and     edx, 7
        sub     ecx, eax
        cmp     edx, 7
        ja      SHORT $LN9@fib
        jmp     DWORD PTR $LN17@fib[edx*4]
$LN10@fib:
        sub     esi, 8
        js      SHORT $LN9@fib
        add     ecx, eax
$LN8@fib:
        add     eax, ecx
$LN7@fib:
        add     ecx, eax
$LN6@fib:
        add     eax, ecx
$LN5@fib:
        add     ecx, eax
$LN4@fib:
        add     eax, ecx
$LN3@fib:
        add     ecx, eax
$LN2@fib:
        add     eax, ecx
        jmp     SHORT $LN10@fib
$LN9@fib:
        pop     esi
        ret     0
        npad    1
$LN17@fib:                      ;jump table
        DD      $LN10@fib
        DD      $LN2@fib
        DD      $LN3@fib
        DD      $LN4@fib
        DD      $LN5@fib
        DD      $LN6@fib
        DD      $LN7@fib
        DD      $LN8@fib
_fib    ENDP

这可能更适用于线性反馈移位寄存器,其中展开循环以节省变量之间的数据移位。例如:

    while(...){
        e = f(a,b,c,d);
        a = b;
        b = c;
        c = d;
        d = e;
    }

展开到

    do{
        a = f(a,b,c,d);
      case 3:
        b = f(b,c,d,a);
      case 2:
        c = f(c,d,a,b);
      case 1:
        d = f(d,a,b,c);
      case 0:
    }while(...);

如果元素的数量不是4的倍数,那么Duff的设备用于进入展开的循环。