跳入 C 中的一个块

Jumping into a block in C

如果我像这个例子一样跳入一个块,跳转 "over" 声明,

#include <stdio.h>

int main(int argc, char *argv[]){
  int counter = 0;
  goto jump;
  {
    static int st = -9;
    int au = -9;
jump:
    printf("st = %d\n", st);
    printf("au = %d\n", au);
    au++;
    st++;
    counter++;
  }

  if(counter < 10) goto jump;

  return 0;
}

我可以用gcc --std=c89 -pedantic编译它。

似乎你不能真正跳转 "over" 声明:变量仍然是 声明的, 即使这些声明所在的行是 从未达到。

但是你可以以某种方式跳过定义...

  1. st 作为静态变量初始化为值 -9 并计数到 0。
  2. au 初始化为 0 并计数到 9。

是 1. and/or 2. C 标准所要求的行为吗?

在 C 中,诸如 st 之类的静态变量在调用 main() 之前在程序启动时进行初始化,即使对于作用域为函数或块的静态变量也是如此。跳过 declaration/initializer 不会影响这一点,因此对于这种特定情况,不会发生未定义、未指定或不确定的行为。

对于自动变量,例如au,在块的执行中到达声明时发生初始化。由于 goto 跳过了块执行的那部分, au 的值仍然不确定,并且在这种情况下使用变量值而不首先将其设置为某个确定值是未定义的行为。

请注意,C++ 中的一些细节有所不同。例如,C++ 标准规定,如果程序跳过声明,则该程序是非良构的,除非该声明针对 POD 类型并且该声明不包含初始化程序。

(1) 是,并且 (2) 是,它将具有不确定的值。
参考文献:C Initialization, C++ Default Initialization

在 C 中,非 static 变量的作用域被定义为从变量声明点到其封闭块的末尾,但其初始化程序被视为语句。尚未设置值的堆栈变量将具有不确定的值,类似于堆变量(使用 malloc() 分配)将具有恰好在分配的内存位置中的任何垃圾值。

另一方面,static 变量总是被声明(但只能在它自己的块中访问)并且基本上在程序启动时就用它的初始化值填充,所以它会有该值一进入范围。

这里是一些手写的程序集,展示了 C 代码如何直接翻译。优化器会进行更改以使代码更小或更快,而编译器会添加一些额外的标记和注释以用于调试、跟踪和其他目的。我可能没有完全正确的语法,但对于我们的目的来说已经足够接近了。

.data
st:            WORD    -9           ;declaration and initialization of st
str1:        STRING    "st = %d\n"
str2:        STRING    "au = %d\n"

.text
main:
push   ebp           ;save base pointer
mov    ebp, esp      ;set base pointer to current stack position

;adjust base pointer for use in referring to function parameters and local variables
add    ebp, 12       ;4 bytes for old base pointer
                     ;4 bytes for argv
                     ;4 bytes for argc
sub    esp, 8        ;reserve 4 bytes for counter (declaration)
                     ;reserve 4 bytes for au(declaration)

mov    [esp], 0      ;initialize of counter

jmp    jump          ;goto jump label

mov    -20[ebp], -9  ;initialize au (THIS IS SKIPPED OVER AND IGNORED)

jump:                ;label, same as in C

push   [st]          ;parameters for printf
push   str1
call   printf        ;call to printf function

push   -20[ebp]      ;parameters for second call to printf
push   str2
call   printf        ;call printf

inc    -20[ebp]      ;increment au
inc    [st]          ;increment st
inc    -16[ebp]      ;increment counter

sub    -16[ebp], 10  ;if(counter == 10)
je     jump          ;jump if equal to jump label

mov    eax, 0        ;set return value

add    esp, 20       ;clean up stack
pop    ebp           ;restore base pointer
ret                  ;return from main

请特别注意,st 是在代码本身中定义并赋值的,而 au 是保存在堆栈中的,只有在代码为它赋值时才会获取它的值。


如果将 au 改为全局变量(即 thread-local),编译器会将 au 放入 .data 部分具有类型的默认值,在这种情况下,代码将更改如下:

.data
;... other data lines
au:            WORD    0            ;declaration and uninitialized (default) value of au

.text
;... other instruction code lines
sub    esp, 4        ;reserve 4 bytes for only counter

;... other instruction code lines
jmp    jump          ;goto jump label
mov    [au], -9      ;assign au (still skipped and ignored)
jump:                ;label, same as in C
;... rest of program (references to au use [au] instead of -20[ebp]