C块范围

时间:2016-02-27 19:58:21

标签: c scope state-machine goto

我试图了解块范围在C中的含义。

我意识到范围内定义的标识符在范围之外是不可见的,但块指令范围在指令级别的含义是什么?进入或退出块范围是否意味着任何指令,还是指令值完全透明?在范围内定义的变量是否被破坏,就像它们在循环结构中一样?

在指令级别,优化后,如下:

initialise:
    int a = 0;
block_entry:
    a += 1;
    /* on first pass (initialisation): a == 1 */
    /* on second pass (entry by goto): a==2 ? */
    if (a==2): goto done

goto block_entry
done:

与...不同:

{
initialise:
    int a = 0;
block_entry:
    a += 1;
    /* on first pass (initialisation): a == 1 */
    /* on second pass (entry by goto): a==2 ? */
    if (a==2): goto done
}

goto block_entry
done:

或来自:

while(1){
initialise:
    int a = 0;
block_entry:
    a += 1;
    /* on first pass (initialisation): a == 1 */
    /* on second pass (entry by goto): a == 2 ? */
    if (a==2): goto done
    goto main_code
}

main_code:
goto block_entry
done:

这个问题主要是学术性的,受到Eli Bendersky's post "Computed goto for efficient dispatch tables"的启发,他似乎只使用while(1) {...}循环来进行视觉结构化。 (具体在interp_cgoto(...)函数中。)

如果他的代码使用块作用域进行可视化结构化或根本没有作用域,那么他的代码是否会执行任何不同的编译? (即删除while(1) {...}循环。)

3 个答案:

答案 0 :(得分:1)

C语言不支持构造函数和析构函数。因此,进入和离开范围不会导致调用“析构函数”。

不同作用域内的变量可以共享相同的内存或寄存器,因此以下代码

{
    char buffer[2048];
    /*...*/
}
{
   char stuff[2048];
   /*....*/
}

根据编译器的决定,可能会耗尽2k或4k的堆栈。理论上它可以创建

union {
   char buff[2048];
   char stuff[2048];
};

因此,如果编译器认为有必要,创建范围允许缩小函数的堆栈和寄存器要求。我在代码中看不到这样的优势。

答案 1 :(得分:1)

在您的第二个和第三个片段中,goto block_entry;导致未定义的行为;而第一个片段没问题(第二遍传递a == 2)。

如果从块外部goto进入块,则在使用初始化器进行变量声明之后;那个变量存在但是没有应用初始化器;变量的行为类似于未初始化的变量。

当块退出时,块内定义的变量在概念上被破坏。这通常不会转换为任何实际的汇编指令,它只会反映在堆栈的哪个位置,你会在函数中找到不同的变量。

答案 2 :(得分:1)

第二个和第三个片段的行为是未定义的,因为变量a的生命周期在声明它的块被退出时结束(即使退出是通过goto)。重新输入块时,会创建一个新的a,其初始值不确定。由于goto跳过声明语句,a的值仍然是不确定的。随后尝试使用该值(a += 1;)会导致未定义的行为。

这是一个实际上似乎在实践中展示未定义行为的例子:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    {
initialise:;
        int a[10] = {0};
block_entry:
        a[0] += 1;
        printf("a is %d\n", a[0]);
        /* on first pass (initialisation): a == 1 */
        /* on second pass (entry by goto): a==2 ? */
        if (a[0]>=2) goto done;
    }
    {
        int x[10];
        x[0] = argc > 1 ? atoi(argv[1]) : 42;
        printf("x is %d\n", x[0]);
    }

    goto block_entry;
done:
    puts("Done");
    return 0;
}

Live on coliru

我修复了一些拼写错误(其中伪代码是C和Python的混合),并添加了另一个可以重用堆栈的块。我将终止条件更改为>=,原因可能很明显。

在精确版本的gcc等中,这会导致a[0]x[0]共享存储,因此第二次循环a是43而不是2。

如果您将数组的大小更改为更小的值,那么gcc不会将它们放在同一堆栈位置,您将获得原始代码段的行为,其中a为2第二遍。

另一方面,如果您使用-O3代替-O0,那么gcc会编译一个无限循环,a始终为1。

所有这些结果都是可以接受的,因为未定义的行为对编译器没有任何限制

简而言之,不要那样做(sm)。