【问题标题】:c99 goto past initializationc99 转到过去的初始化
【发布时间】:2011-02-18 18:48:09
【问题描述】:

在调试崩溃时,我在一些代码中遇到了这个问题:

int func()
{
    char *p1 = malloc(...);
    if (p1 == NULL)
        goto err_exit;

    char *p2 = malloc(...);
    if (p2 == NULL)
        goto err_exit;

    ...

err_exit:
    free(p2);
    free(p1);

    return -1;
}

当第一个 malloc 失败时会出现问题。因为我们跳过了p2的初始化,它包含随机数据并且对free(p2)的调用可能会崩溃。

我希望/希望这将被视为与 C++ 中相同的方式,其中编译器不允许 goto 跳过初始化。

我的问题:是跳过标准允许的初始化还是 gcc 的 c99 实现中的错误?

【问题讨论】:

  • 看到一些程序如何通过跳过初始化来知道它们在做什么,我打赌“它是允许的”,但这是我时区延长周末的前一天晚上,所以我不是在查。不过问题很好。
  • 有趣 - 就我一眼看得出,C99 标准对 goto 过去的声明/初始化保持沉默,只是它说您不能从外部跳转到具有 VLA 的范围范围。再说一次,我可能没有在所有正确的地方寻找......
  • 这是一个很好的例子,说明了为什么 goto “被认为是有害的”。我并不完全反对它,但它可以让这样的事情变得容易但不那么明显。
  • @nategoose:不幸的是,这种错误处理是 goto 具有合法用途的领域之一(无论如何我认为)。太糟糕了,即使这样使用它仍然很容易出错
  • @Michael Burr:我也很抱歉。我不认为你的评论是负面的,我也不打算给自己留下负面的印象。由于很多人(理论上)从这里发布的内容中学习,我不希望有人知道goto 本质上是好是坏,甚至是进行错误处理或不进行错误处理的方式。有时它是最好用的东西,有时是最坏的东西,有时它是最好的但用得非常糟糕。

标签: c gcc c99 goto


【解决方案1】:

这不是 gcc 中的错误。跳转只是 C 中的跳转。没有应用特殊逻辑。问题是您没有首先初始化指向NULL 的指针。如果你这样做,那么你的免费电话将是free(NULL),它不会崩溃。以char *p1 = NULL, *p2 = NULL; 启动函数,一切都会好起来的。

【讨论】:

  • 将变量定义得更接近它们的使用位置。在函数末尾集中错误处理并在错误时跳转到它也更干净。
  • 我原则上同意并且我喜欢您的解决方案,但是如果您可以在一个屏幕上看到整个函数,那么您声明变量的位置就变得没有意义了。如果你不能在一个屏幕上看到整个功能,那你就有更大的问题了。
  • 是的,我同意指针和所有其他变量在使用它们之前应该被初始化。在示例中,您引用了一个未初始化的变量。
  • +1;您可以使用反引号标记答案中的代码部分
【解决方案2】:

您可以使用-Wjump-misses-init 让gcc 在您跳过变量定义时发出警告,然后您可以使用-Werror(或更准确地说,-Werror=jump-misses-init)来强制用户处理它。此警告包含在 -Wc++-compat 中,因此 gcc 开发人员知道代码在 C 和 C++ 中的行为不同。

你也可以稍微修改一下代码:

int func()
{
    char *p1 = malloc(...);
    if (p1 == NULL)
        goto err_exit_1;

    char *p2 = malloc(...);
    if (p2 == NULL)
        goto err_exit_2;

    ...

err_exit_2:
    free(p2);
err_exit_1:
    free(p1);

    return -1;
}

... 并保持将标签与初始化变量配对。使用单元化变量调用许多其他函数也会遇到同样的问题,free 恰好是一个更明显的问题。

【讨论】:

  • 您实际上可以做得更好--Werror=jump-misses-init 只会将该警告变成错误(我认为这从 gcc 4.3 开始可用)。
  • 这段代码有错误。如果任一 malloc 失败,您仍然至少尝试一次 free(NULL)
  • @nategoose - free(NULL) 是合法的,被定义为无操作。
  • @nategoose: On 可以称之为“低效率”,但这不是错误。 free(NULL) 是完全合法的做法。
【解决方案3】:

使用 goto 并不是一个聪明的主意,而您刚刚找到了一个原因。您应该为每个单独的错误调用错误处理函数。

【讨论】:

  • 问题不是特定于goto。同样的问题可以用其他类型的跳转来重现,比如switch/case 语句。
  • 这太傻了。 goto 有非常有用的应用程序,它在这里的使用可以在几乎所有主要的 C 项目中看到镜像,包括 Linux 内核。使用goto 进行错误处理是对goto 的公认“好”用法。停止推进对被误解的语言功能的无知和明确禁止!
  • 这个答案完全是老错误。弗洛林的回答是这里的标准习语。
【解决方案4】:

标准确实允许这样的跳转,所以这不是 GCC 中的错误。该标准将这种情况列为附录 I 中的建议警告。

在 C99 中对范围跳转的唯一限制是,跳转到可变修改类型的变量(如 VLA)的范围是非法的

int main() {
  int n = 5;
  goto label; // <- ERROR: illegal jump
  int a[n];
label:;
}

换句话说,说“跳转只是 C 中的跳转”是不正确的。在进入变量范围时,跳转会受到一定的限制,尽管不像 C++ 中那样严格。你描述的情况不是限制的情况之一。

【讨论】:

    【解决方案5】:

    如果我用 -O2 标志编译这段代码

    gcc -Wall -std=c99 -O2 jump.c
    

    我收到警告:

    jump.c: In function ‘func’:
    jump.c:10: warning: ‘p2’ may be used uninitialised in this function
    

    没有优化就没有警告

    【讨论】:

      【解决方案6】:

      作为AndreyT says,C99 允许跳过初始化。您可以通过对两个故障使用单独的标签来修复您的逻辑:

      int func()
      {
          char *p1 = malloc(...);
          if (p1 == NULL)
              goto err_exit_p1;
      
          char *p2 = malloc(...);
          if (p2 == NULL)
              goto err_exit;
      
          ...
      
      err_exit:
          free(p2);
      err_exit_p1:
          free(p1);
      
          return -1;
      }
      

      这是一种标准模式——“早期错误”会导致跳转到错误退出代码的后面部分。

      【讨论】:

      • 我真正关心的是如何确定像这样的代码可能在哪里进入了我们的代码库。
      • @R Samuel Klatchko:如果您使用-Wuninitialized(需要-O1 或更高版本)进行编译,那么您将收到警告:foo.c:10: warning: ‘p2’ may be used uninitialized in this function
      • 感谢您的建议。不幸的是,-Wuninitialized 没有为我发现这个(我们仍在使用 gcc 3.4.5)。
      【解决方案7】:

      嗯,并不是因为新标准允许在任何地方声明变量,所以使用它总是一个好主意。在你的情况下,我会像我们在经典 C 中那样做。

      int func()
      {
      char *p1 = NULL;    /* So we have a defined value */
      char *p2 = NULL;
      
        p1 = malloc(...);
        if(!p1)
          goto err_exit;
      
        p2 = malloc(...);
        if(!p2)
          goto err_exit;
      
        ...
      
        err_exit:
          free(p2);
          free(p1);
      
        return -1;
      }
      

      【讨论】:

      • +1 对于诸如此类不需要单独标签的“早期错误”的简单情况,我更喜欢只使用一个 goto 标签。
      猜你喜欢
      • 2021-11-22
      • 2015-10-30
      • 2021-09-26
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 2019-01-16
      • 1970-01-01
      相关资源
      最近更新 更多