转到变量定义之前 - 它的值会发生什么?

发布于 2024-11-25 15:42:02 字数 377 浏览 3 评论 0原文

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

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 ?或者,当 gotoa 之前的位置时,编译器是否可以占用存储 a 的寄存器?

Here is some question I wondered about. Given the following code, can we be certain about its output?

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; 
}

Is this guaranteed to output finished: 10 according to the C++ Standard? Or can the compiler occupy the register that a is stored into, when goto to a place prior to a?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

季末如歌 2024-12-02 15:42:02

6.7/3 说

从局部变量所在的点跳转的程序
自动存储持续时间不在其所在范围内
范围是不正确的,除非变量具有 POD 类型 (3.9) 并且是
声明时不使用初始值设定项 (8.5)。

所以应该没问题。

然后在6.6/2中:

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

现在,这对我来说意味着,当您跳回到 z 时,a 就完蛋了,并且您无法对 a 的无初始化器声明如何做出任何保证code> 将在第二次执行时表现出来。

参见 6.7/2:

具有自动存储期限(3.7.2)的变量分别被初始化
他们的声明语句被执行的时间。带自动变量
块中声明的存储持续时间在退出时被销毁
块(6.6)。

所以在我看来,并不能保证你会得到 10,尽管似乎很难想象编译器不会出现这种情况。

6.7/3 says that

A program that jumps from a point where a local variable with
automatic storage duration is not in scope to a point where it is in
scope is illformed unless the variable has POD type (3.9) and is
declared without an initializer (8.5).

So that should be ok.

Then in 6.6/2:

On exit from a scope (however accomplished), destructors (12.4) are
called for all constructed objects with automatic storage duration
(3.7.2) (named objects or temporaries) that are declared in that
scope, in the reverse order of their declaration.

Now this implies to me that a is toast when you jump back to z and you cant' make any guarantees about how the no-initializer declaration of a will behave the second time it executes.

See 6.7/2:

Variables with automatic storage duration (3.7.2) are initialized each
time their declarationstatement is executed. Variables with automatic
storage duration declared in the block are destroyed on exit from the
block (6.6).

So it looks to me like there isn't a guarantee that you'll get 10 although it seems hard to imagine a compiler where that wouldn't be the case.

内心激荡 2024-12-02 15:42:02

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


我没有可用的 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 不是“初始化”的,如果我正确理解标准术语,它也不是一个对象。

Note: Read the comments to this one first. Johannes more or less shot down my whole argument with one well-placed standard quote. ;-)


I do not have the C++ standard available, so I have to extrapolate from the C standard.

Surprisingly enough (for me), chapter 6.2.1 Scopes of identifiers says nothing about the scope of an identifier starting at the point of its declaration (as I would have guessed). int a, in your example, has block scope, which "terminates at the end of the associated block", and that is all that is said about it. chapter 6.8.6.1 The goto statement says that "a goto statement shall not jump from outside the scope of an identifier having a variably modified type to inside the scope of that identifier" - but since your gotos jump around only within the block (and, thus, the scope of int a, that seems to be OK as far as ISO/IEC 9899:1999 is concerned.

I'm quite surprised about this...

Edit #1: A quick google later I got my hands on the C++0x final draft. The relevant statement, I think, is this here (6.7 Declaration statement, highlighting mine):

It is possible to transfer into a block, but not in a way that
bypasses declarations with initialization.
A program that jumps from a point where a variable with automatic
storage duration is not in scope to a point where it is in scope is
ill-formed unless the variable has scalar type, class type with
a trivial default constructor and a trivial destructor, a cv-qualified
version of one of these types, or an array of one of the
preceding types and is declared without an initializer.

I think your code is OK by the standard's standards. But butt-ugly, mind you. ;-)

Edit #2: Reading your comment about the possible destruction of int a due to the jump backwards, I found this (6.6 Jump statements, highlighting mine):

Transfer out of a loop, out of a block, or back past an initialized variable
with automatic storage duration involves the destruction of objects with
automatic storage duration that are in scope at the point transferred from but
not at the point transferred to.

One, int a is not "initialized", and it is not an object if I understand the standard terminology correctly.

我不吻晚风 2024-12-02 15:42:02

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

It's not allowed to forgo a variable definition. It should be an error.

吃素的狼 2024-12-02 15:42:02

根据C++标准,是否保证输出finished: 10

我想是的,必须的!

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

Is it guaranteed to output finished: 10 according to the C++ Standard?

I think yes ,it must!

Why ? Because a lives from its declaration till the end of its scope (end of the function) and by definition it can only be initialized once and from there on retain its value until destruction which is at the end of the function.

塔塔猫 2024-12-02 15:42:02

为什么不通过装配来确定这一点?

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

理由如下。


我简化了问题:

main.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

它陷入了无限循环。

Why not determine this with assembly?

Conclusion: The local variable will only be declared once; however, the assignments will all be performed each time.

Justification below.


I simplified the problem:

main.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;
}

Then I compiled this into assembly (with the asm's uncommented) on Linux with:

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

which output main.s (the assembly output of main.cpp). Here is the relevant part; I added in a bunch of dashes to make it more obvious:

#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()

Then, I compiled main.cpp using:

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

It compiled fine. Then I ran it with:

./main

And it was stuck in an infinite loop.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文