在变量定义之前转到 - 其值会发生什么

Goto prior to a variable definition - what happens with its value?

本文关键字:什么 定义 变量      更新时间:2023-10-16

这是我想知道的一些问题。给定以下代码,我们能确定它的输出吗?

void f() {
  int i = 0; 
  z: if(i == 1) goto x; else goto u; 
  int a; 
  x: if(a == 10) goto y; 
  u: a = 10; i = 1; goto z; 
  y: std::cout << "finished: " << a; 
}

这是否保证根据C++标准输出finished: 10?或者编译器可以在goto a之前的位置时占用存储a的寄存器?

6.7/3 说

从局部变量具有 自动存储持续时间不在范围内,无法达到它所在的点 除非变量具有 POD 类型 (3.9( 并且 声明时不带初始值设定项 (8.5(。

所以这应该没问题。

然后在 6.6/2 中:

从作用域退出时(无论如何完成(,析构函数 (12.4( 为 调用具有自动存储持续时间的所有构造对象 (3.7.2((命名对象或临时对象(中声明的 范围,与其声明的顺序相反。

现在这对我来说意味着,当你跳回z时,a是烤面包,你不能保证 a 的无初始值设定项声明在第二次执行时的行为方式。

见6.7/2:

具有自动存储持续时间 (3.7.2( 的变量将分别初始化 执行声明语句的时间。具有自动功能的变量 块中声明的存储持续时间在退出时销毁 块 (6.6(。

所以在我看来,不能保证你会得到 10 个,尽管似乎很难想象

编译器不会是这种情况。

注意:请先阅读对此的评论。约翰内斯或多或少地用一个恰当的标准引用来驳斥我的整个论点。;-)


我没有可用的C++标准,所以我必须从C标准进行推断。

令人惊讶的是(对我来说(,第 6.2.1 章标识符的范围没有说明标识符的范围从其声明点开始(正如我所猜测的那样(。 在您的示例中,int a具有块范围,它"在关联块的末尾终止",这就是关于它的全部内容。第 6.8.6.1 章 goto 语句说">goto 语句不得从具有可变修改类型的标识符的范围之外跳转到该标识符的范围内"——但由于您的goto仅在块跳转(因此,int a的范围(,就 ISO/IEC 9899:1999 而言,这似乎是可以的。

我对此感到非常惊讶...

编辑#1:稍后快速谷歌,我得到了C++0x最终草案。我认为,相关声明是这样的(6.7声明声明,突出我的声明(:

可以转移到一个块中,但不能以 通过初始化绕过声明。 从具有自动变量的点跳转的程序 存储持续时间不在范围内,以至于它在范围内 格式不正确,除非变量具有标量类型,类类型具有 一个普通的默认构造函数和一个普通析构函数,一个符合 CV 条件的构造函数 这些类型之一的版本,或其中一种类型的数组 前面的类型,声明时不带初始值设定项

我认为按照标准的标准,您的代码还可以。但是屁股丑陋,请注意。;-)

编辑#2:阅读您关于由于向后跳转而可能破坏int a的评论,我发现了这个(6.6 跳转语句,突出显示我的(:

出循环、转出块或返回初始化变量 具有自动存储持续时间涉及破坏对象 在传输点在范围内的自动存储持续时间,但 不在转移到的点。

第一,int a不是"初始化"的,如果我正确理解标准术语,它就不是一个对象。

不允许放弃变量定义。这应该是一个错误。

是否保证按照C++标准输出finished: 10

我认为是的,它必须!

为什么?因为a从它的声明一直存在到它的作用域结束(函数的结束(,并且根据定义,它只能初始化一次,从那里开始保留其值,直到函数结束时的销毁。

为什么不用汇编来确定呢?

结论:局部变量只会声明一次;但是,每次都会执行赋值。

理由如下。

<小时 />

我简化了问题:

主.cpp:

int f() {
  //asm(";start f()");
  //asm(";begin: ");
  begin:
  int i = 0;
  if (i == 0)
  {
    i++;
    goto begin;
  }
  //asm(";after goto");
  return i + 2;
}
int main()
{
  //asm(";before f()");
  int i = f();
  //asm(";after f()");
  return i;
}

然后我把它编译成Linux上的汇编(asm未注释(如下:

g++ -std=c++20 -I/usr/include/c++/11 -I/usr/include/x86_64-linux-gnu/c++/11 -S main.cpp

其中输出 main.s(main.cpp 的程序集输出(。这是相关部分;我添加了一堆破折号以使其更明显:

#APP
# 2 "main.cpp" 1
-----------------------------------;start f()
# 0 "" 2
# 4 "main.cpp" 1
-----------------------------------;begin:
# 0 "" 2
#NO_APP
.L2:
    movl    $0, -4(%rbp)
    cmpl    $0, -4(%rbp)
    jne .L3
    addl    $1, -4(%rbp)
    jmp .L2
.L3:
#APP
# 13 "main.cpp" 1
-----------------------------------;after goto
# 0 "" 2
#NO_APP
    movl    -4(%rbp), %eax
    addl    $2, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   _Z1fv, .-_Z1fv
    .globl  main
    .type   main, @function
main:
.LFB1:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
#APP
# 19 "main.cpp" 1
-----------------------------------;before f()
# 0 "" 2
#NO_APP
    call    _Z1fv
    movl    %eax, -4(%rbp)
#APP
# 21 "main.cpp" 1
-----------------------------------;after f()

然后,我编译了main.cpp使用:

g++ -std=c++20 -I/usr/include/c++/11 -I/usr/include/x86_64-linux-gnu/c++/11 main.cpp -omain

它编译得很好。然后我用:

./main

它陷入了一个无限循环。

相关文章: