【问题标题】:goto and automatic variables initializer in cc中的goto和自动变量初始化器
【发布时间】:2012-07-12 08:13:31
【问题描述】:

在教程中说

如果使用 goto 语句跳转到块的中间,该块中的自动变量不会被初始化。

那么在下面的代码中,如果我可以被访问/声明,那么为什么它没有被初始化?

int main()
{
   goto here;
   {
     int i=10;
     here:
      printf("%d\n",i);
   }
   return 0;
}

ps:输出是一些垃圾值。

【问题讨论】:

  • 明确地写下你的初始化,比它更清楚:int i;i=10;

标签: c


【解决方案1】:

您的问题“如果可以访问i,为什么...”背后没有逻辑。能够“访问i”并不是支持或反对任何事情的理由。这只是意味着printf 语句与i 在同一范围内。但是,由于您跳过了初始化程序,因此变量未初始化(正如您的教程所说)。

读取未初始化的变量是未定义的行为,因此您的程序格式错误。

变量i 的内存在编译时已经被留出,因为已知该变量存在于内部块中。正如您想象的那样,内存不会动态分配。它已经存在,但由于goto,它从未设置为任何确定的值。

经验法则:不要跳过初始化程序。

【讨论】:

  • 当编译器编译指令块的时候会是这样的,push i然后初始化i的指令,那么当goto语句直接跳转到printf指令时push i是如何执行的?我不能明白了
  • 这很有趣 - 所以编译器会看到语句 int i=10;并在堆栈上为 int 分配足够的内存,但 将其设置为 10。如果语句被拆分,例如诠释我;我=10;我们可以认为编译器执行第一条语句但跳过第二条语句。这是一个很好的问题 - 让你思考
  • @Hiett:如果它有助于记住发生了什么,那么变量声明不是 C 语法中的语句。块中的所有声明都有助于编译器理解当代码进入该块时它需要在堆栈上提供哪些空间,这就是为什么在 C89 中它们必须位于块的开头,在任何语句之前。这些变量的初始化器和赋值与调用总共(比如说)4个字节的自动变量的知识有些分开。
  • @vindhya:如果您可以读取程序集,您可以检查发出的代码,但可能发生的情况是在goto 的位置,编译器会在跳转之前调整堆栈指针。另一种可能性是编译器实际上已经“提升”了块外变量i的空间,因此在函数开始时为其腾出空间,并且堆栈指针在函数入口处移动一次,然后进入和离开街区时不再移动。
  • 澄清“i 的内存已在编译时留出”(可能会产生误导):i 是一个局部变量,每次函数调用时都会在堆栈上为其创建空间被调用并在函数退出时恢复堆栈 - 编译器添加了代码以在堆栈上创建空间并将变量替换为该空间的地址,在这种情况下 [rbp-4] 其中 4 是 int 的大小跨度>
【解决方案2】:

变量在声明它们的范围内是可见的(在这种情况下,在{} 之间),与该范围内语句的执行顺序无关。 goto 绕过了i 的初始化,这意味着在调用printf() 时它具有未定义的值。

【讨论】:

    【解决方案3】:

    考虑另一种明显的情况:

    int main()
    {
        int i; //i is declared, but not initialized
        goto here;
        {
           i=10;//i is initialized 
           here: //you've skipped the initialization
           printf("%d\n",i);//and got garbage
        }
    return 0;
    }
    

    在你的情况下:

    int main()
    {
    
        goto here;
        {
           //printf("%d\n",i);  // i does not exist here yet
           int i; //from here until the end of the scope variable i exists
           i=10;  // i exists here and smth is written into it
       here:  // i exists here
           printf("%d\n",i); // i exists here and it's value is accessed
        }
    return 0;
    }
    

    所以,int i = 5; 这真的是两件事。一个是声明,不能被任何东西跳过,包括goto(就像打开新范围也不受影响。你已经跳到范围的中间,但范围已经在那里)。其次是操作赋值,因为是正常操作(程序流程),所以可以被goto或者'break'或者'continue'或者'return'跳过

    【讨论】:

    • 虽然我怀疑您在口语意义上使用“初始化”,但 c 正式将“赋值”和“初始化”视为独立的事物,即使它们都可以用 = 表示。只需尝试 int a[3]; a = { 1, 2, 3};int a[3] = {1, 2, 3}; 相比。
    • @dmckee 我在某种意义上使用了“初始化”,即“将值分配给以前从未分配过且包含垃圾的变量”。数组初始值设定项本身就是一个 hack 和异常。因此,不考虑它会使图像更清晰。
    • 我理解,但标准使用“赋值”和“初始化”这两个词,在正式上下文中它们是不同的。当您非正式地写作时,这会很痛苦,因为总是有引入混乱的风险。
    • @dmckee 该标准使用了许多与常识相反的词,并坚持在实现完美定义的所有其他行为上调用 UB。所以恕我直言,标准最好用来让互联网对手眼花缭乱,而不是真正帮助某人理解某些东西。
    【解决方案4】:

    C 编译器将解析源文件并“记录”任何变量初始化。
    当它到达

    printf("%d\n", i)
    

    它将知道变量 i 已经存在并且他应该能够使用它,因为它在范围内。
    At 执行空间是在 main 函数调用之后,main() 中的任何代码执行之前,堆栈上的 i 变量保留的。

    【讨论】:

      【解决方案5】:

      因为语言标准是这样说的:

      6.7.8 初始化

      语义

      如果具有自动存储持续时间的对象未显式初始化,则其值是不确定的。

      J.2 未定义行为

      在以下情况下行为未定义:

      具有自动存储期限的对象的值在不确定时使用。

      6.8.4.2 switch 语句

      EXAMPLE 在仿真程序片段中

      switch (expr)
      {
        int i = 4;
        f(i);
        case 0:
          i = 17;
          /* falls through into default code */
        default:
          printf("%d\n", i);
      }
      

      标识符为 i 的对象存在自动存储持续时间(在块内)但从未初始化,因此如果控制表达式具有非零值,则对 printf 函数的调用将访问一个不确定的值。同样的,函数 f 的调用也无法到达。

      【讨论】:

      • 附录 J 仅供参考。实际上 6.3.2.1 p2(根据 C11)更加精确和规范。如果auto 变量可以用register 声明,则只有UB,即它的地址永远不会被占用。
      • @JensGustedt 该值仍不确定,最好避免潜在的 UB。
      • 在大多数架构(没有陷阱表示)上,它只是一个未指定的值,它应该已经足以说服所有人,不要做这样的事情:)
      • @JensGustedt 我知道,签名溢出也是如此。
      【解决方案6】:

      C 允许您访问地址空间内的任何内容,无论它是否实际初始化。有时像那样工作会崩溃或显示垃圾,有时它会打印一些有用的东西,但这都是未定义的行为。很方便的把戏,但却是破坏程序的好方法,所以不要认为仅仅得到结果就意味着你的把戏有效。

      【讨论】:

      • C 肯定不会“让你在任何地方访问任何东西”。
      • 您可以将一个指向您想要的任何数字的指针放入 printf,它会愉快地打印该内存位置的内容,就好像它是一个局部变量一样。也许我应该符合“在您的地址空间内”的条件,但您肯定知道我的意思吗?
      • 变量仍然是作用域的,所以它不像许多其他语言,你定义的所有东西都可以从其他任何地方读取。您仍然必须遵守规则。此外,指针语义有严格的规则,你很可能在谈论某种未定义的行为......
      • 如果你在提问者的代码中写return i;而不是return 0;,那么C不会让你在任何地方访问任何东西。问题似乎是关于i 如何在范围内但未初始化,而不是关于 C 慷慨地让你通过使用指针在地址空间中漫游来击中自己的脚 :-)
      • 当然 C 允许您使用指针访问任何内容(在您的地址空间内......),但这不是问题的重点。不涉及指针,只是“正常”变量。所以访问变量不是技巧,它只是访问一个变量。跳过初始化很糟糕......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-26
      • 1970-01-01
      相关资源
      最近更新 更多