我试图了解块范围在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) {...}
循环。)
答案 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;
}
我修复了一些拼写错误(其中伪代码是C和Python的混合),并添加了另一个可以重用堆栈的块。我将终止条件更改为>=
,原因可能很明显。
在精确版本的gcc等中,这会导致a[0]
和x[0]
共享存储,因此第二次循环a
是43而不是2。
如果您将数组的大小更改为更小的值,那么gcc不会将它们放在同一堆栈位置,您将获得原始代码段的行为,其中a
为2第二遍。
另一方面,如果您使用-O3
代替-O0
,那么gcc会编译一个无限循环,a
始终为1。
所有这些结果都是可以接受的,因为未定义的行为对编译器没有任何限制。
简而言之,不要那样做(sm)。