【问题标题】:Is a goto in alloca's function scope valid?alloca 函数范围内的 goto 是否有效?
【发布时间】:2014-07-12 11:05:21
【问题描述】:

C 标准禁止 goto 进入存在 VLA 的函数范围。

VLA 和对 alloca 函数的调用在低级别应该具有相同的结果。

(我可能是错的,因为我只是一个 C,不是低级程序员,但在我的想象中似乎很机智)

那么下面的 sn-p 也会是未定义的行为吗?

int main()
{
    char *p;

    goto label1;

    {
        p = _alloca(1);
label1:
        p = NULL;
    }
}

当然我不能引用p,但是行为呢?

【问题讨论】:

  • 没有首字母缩写词 - 在这种情况下什么是 VLA?
  • @cup 它在 C 标准文档中是“可变长度数组”的缩写,所以类似于 char Array[variableLength];

标签: c c99 undefined-behavior alloca


【解决方案1】:

C 标准对alloca() 的行为没有任何规定。一些编译器以非常可预测的方式使用堆栈,并使用大量冗余的帧指针访问自动变量。在这样的编译器上,可以通过简单地从堆栈指针中减去一个值来保留堆栈上的空间,而编译器不必知道或关心有问题的保留。但是,如果编译器以应用程序未预期的方式使用堆栈,则此类代码将严重损坏。

我不认为是这样的:

  int *p = 0;
  if (!foo(1,2,3))
    goto skip_alloca;
  p=alloca(46);
skip_alloca:
  bar(4,5,6);
  ...

往往比以下更危险:

  int *p = 0;
  if (foo(1,2,3))
    p=alloca(46);
  bar(4,5,6);
  ...

如果在执行alloca() 时函数调用的堆栈上没有残留物,则任一操作都可能是安全的。如果在分配时堆栈上有残留物(例如,因为编译器选择将清理 foo 的参数推迟到调用 bar 之后),这将使 alloca() 行为不端。使用goto 版本的代码实际上可能比使用if 的版本更安全,因为编译器更难确定推迟foo 的清理可能是有利的。

【讨论】:

    【解决方案2】:

    实际上,规则 6.8.6.1 规定:

      A goto statement is not allowed to jump past any declarations of objects 
      with variably modified types.
    

    在您的代码中,不存在具有可变修改类型的对象。 alloca 确实 not 声明了一个对象(编译器必须处理)。因此,没有什么比 alloca 更适合的范围,也没有规则 6.8.6.1 意义上的未定义行为的理由。

    编辑

    稍微详细说明一下答案:在 VLA 的情况下,行为的“不确定性”是由于声明对象在其范围内“已知”的承诺(在语言级别)。通常,声明设置代码执行的上下文。它不需要在运行时执行。然而,在 VLA 的情况下情况并非如此:这里的这个承诺部分是在运行时实现的,打破了 C 的静态声明方法。为避免可能导致动态类型系统的进一步冲突,规则 6.8.6.1 避免了此类冲突。

    相比之下,在语言级别alloca 只是一个函数;它的调用不构成任何范围。它只承诺它的运行时行为,以防它被调用。如果它没有被调用,我们就不会从函数中“期待”任何东西。因此,它的纯粹存在不会引发任何冲突:两种情况(绕过或非绕过)都有明确定义的语义。

    【讨论】:

    • alloca 是非标准的,所以标准无论如何都不会说什么。
    • @Potatoswatter:即使它是非标准的,它也根本没有声明任何东西。因此,VLA 的问题与这里无关。
    • 我同意,但概念可以更清楚一点。答案的当前措辞似乎只是根据 6.8.6.1 评估 alloca 调用。
    • @Potatoswatter 但无论如何它都可能导致未定义的行为,不是吗?
    • @Zaibis alloca 的理想行为与编译器的特定文档所述相同。它通常不与控制流或作用域交互。所以,你应该没事。
    【解决方案3】:

    VLA 和对 alloca 函数的调用在低级别应该具有相同的结果。

    仍有一些差异。 VLA 对象在其声明的作用域结束时被丢弃,alloca 分配的内存对象在函数返回时被丢弃。

    这有所不同,因为 c99, 6.8.6.1p1 中的要求(“goto 语句不得从具有可变修改类型的标识符范围之外跳转到该标识符范围内” em>) 关注具有可变修改类型的对象的运行时分配/解除分配。这里alloca语句在goto执行后不会执行,所以我认为goto调用不会调用未定义的行为。

    【讨论】:

    • 啊啊啊,我什至没有想到这个区别。所以即使在离开理论上调用 alloca 的范围之后,在我们到达返回之前,内存仍然是可引用的,我们离开了块范围无关紧要吗?在其他情况下,您是否认为 alloca 会以另一种方式导致 UB 关于 goto 规则?
    • @Zaibis 如果标签在您的示例中的 alloca 语句之后,则不会调用 alloca 函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    • 2011-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-20
    • 1970-01-01
    相关资源
    最近更新 更多