【问题标题】:How can a variable be used when its definition is bypassed?绕过定义时如何使用变量?
【发布时间】:2011-12-16 14:41:49
【问题描述】:

在我看来,定义总是意味着存储分配。

在下面的代码中,int i 在程序堆栈上分配一个 4 字节(通常)的存储空间并将其绑定到 ii = 3 将 3 分配给该存储空间。但是因为goto,定义被绕过,这意味着没有为i分配存储空间。

我听说局部变量要么在它们所在的函数入口(在本例中为f())分配,要么在定义点分配。

但无论哪种方式,i 在尚未定义的情况下如何使用(根本没有存储空间)?执行i = 3时赋值3在哪里?

void f()
{
    goto label;
    int i;

label:
    i = 3;
    cout << i << endl; //prints 3 successfully
}

【问题讨论】:

  • 您将代码编译顺序与控制流混淆了

标签: c++ scope variable-declaration


【解决方案1】:

长话短说; goto 将导致运行时跳转,变量定义/声明将导致存储分配,编译时间。

编译器将查看并决定为 int 分配多少存储空间,它还会在“命中”i = 3; 时将此分配的存储空间设置为 3

即使在你的函数的开头有一个goto,在声明/定义之前,那个内存位置也会在那里,就像你的例子一样。


非常愚蠢的比喻

如果我把一根木头放在地上,我的朋友跑(闭着眼睛)跳过它,木头仍然会在那里——即使他没有看到或感觉到它。

如果他愿意的话,可以说他可以转身(在稍后的时间)并将其点燃,这是现实的。他的跳跃并没有让原木神奇地消失。

【讨论】:

  • 忍不住为这个比喻点赞!
  • 这对我来说仍然很奇怪。如果不执行int i; 行,则逻辑希望 i 未定义!编译器知道这条指令不会被执行,所以在我看来,我应该被跳过并且我没有定义。但是,显然,c++ 标准规则不同......在另一个响应中跳过 A a 与 A 类,将导致编译时崩溃。这就是我所期望的。或所有变量...
  • 如果我们将 int i 改为 std::string i="abc"; ? goto 是否对它传递的对象强制执行构造函数调用?
【解决方案2】:

您的代码很好。如果goto 不存在,该变量将存在于它所在的任何位置。

请注意,有些情况下您不能跳过声明:

C++11 6.7 声明语句[stmt.dcl]

3 可以转移到块中,但不能以绕过初始化声明的方式。一种 从具有自动存储持续时间的变量不在范围内的点跳转到 它在范围内的点是格式错误的,除非变量具有标量类型,类类型具有微不足道的默认值 构造函数和普通析构函数,其中一种类型的 cv 限定版本,或其中一种类型的数组 前面的类型并且在没有初始化程序的情况下声明(8.5)。 [ 例子:

void f()
{
    // ...
    goto lx;    // ill-formed: jump into scope of `a'
    // ...
ly:
    X a = 1;
    // ...
lx:
    goto ly;    // ok, jump implies destructor
                // call for `a' followed by construction
                // again immediately following label ly
}

——结束示例]

【讨论】:

    【解决方案3】:

    定义不是可执行代码。它们只是给编译器的指令,让它知道变量的大小和类型。从这个意义上说,goto 语句并没有绕过定义。

    如果您使用带有构造函数的类而不是int,则goto 将绕过构造函数的调用,但无论如何都会分配存储空间。但是,类实例将保持未初始化状态,因此在其定义/初始化行获得控件之前使用它是错误的。

    【讨论】:

      【解决方案4】:

      在我看来,定义总是意味着存储分配。

      这是不正确的。编译器在为函数创建堆栈布局时会保留变量的存储空间。 goto 只是绕过初始化。由于您在打印之前分配了一个值,所以一切都很好。

      【讨论】:

        【解决方案5】:

        流的控制与编译器在编译时保留的变量存储无关。

        goto 语句仅影响对象的动态 初始化。对于内置类型和 POD 类型,这无关紧要,因为它们可以保持未初始化状态。但是,对于非 POD 类型,这将导致编译错误。例如看这个

        struct A{ A(){} };  //it is a non-POD type
        
        void f()
        {
            goto label;
        
            A a;     //error - you cannot skip this!
        
        label:
            return;
        }
        

        错误:

        prog.cpp: In function ‘void f()’:
        prog.cpp:8: error: jump to label ‘label’
        prog.cpp:5: error:   from here
        prog.cpp:6: error:   crosses initialization of ‘A a’
        

        请看这里:http://ideone.com/p6kau

        在这个例子中A是一个非POD类型because it has user-defined constructor,这意味着对象需要被动态初始化,但是由于goto语句试图跳过这个,编译器应该会产生错误。

        请注意,只有内置类型和 POD 类型的对象可以保持未初始化状态。

        【讨论】:

          【解决方案6】:

          简而言之,变量声明是词法的,属于词法{}-enclosed 块。绑定从声明它的行到块的末尾都是有效的。它不受流量控制的影响 (goto)。

          另一方面,locol(堆栈)变量的变量赋值是当控制流到达那里时执行的运行时操作。所以goto对此有影响。

          当涉及到对象构造时,事情会变得有点棘手,但这不是你的情况。

          【讨论】:

            【解决方案7】:

            i 的声明位置与编译器无关。您可以通过在 goto 之前使用 int i 编译您的代码,然后在之后比较生成的程序集来证明这一点:

            g++ -S test_with_i_before_goto.cpp -o test1.asm
            g++ -S test_with_i_after_goto.cpp -o test2.asm
            diff -u test1.asm test2.asm
            

            在这种情况下,唯一的区别是源文件名 (.file) 引用。

            【讨论】:

              【解决方案8】:

              变量的定义不为变量分配内存。它确实告诉编译器准备适当的内存空间来存储变量,但是当控制传递定义时,内存并没有分配。

              这里真正重要的是初始化。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-09-10
                • 1970-01-01
                • 2011-11-02
                • 1970-01-01
                • 2012-05-29
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多