为啥我可以进入 alloca:d 变量的范围,而不是可变长度数组?

Posted

技术标签:

【中文标题】为啥我可以进入 alloca:d 变量的范围,而不是可变长度数组?【英文标题】:Why can I goto into the scope of a alloca:d variable, but not a variable length array?为什么我可以进入 alloca:d 变量的范围,而不是可变长度数组? 【发布时间】:2017-11-04 01:49:23 【问题描述】:

查看这个测试程序:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])

  if (argc < 2)
    goto end;

  char s[strlen(argv[1]) + 1];
  strcpy(s, argv[1]);
  printf("s=%s\n", s);

end:
  return 0;

编译失败,出现错误“跳转到具有可变修改类型的标识符范围”(参见other question)。

但是,如果我将s 的声明更改为此(并包括alloca.h),它编译得很好:

char *s = alloca(strlen(argv[1]) + 1);

为什么 C 标准允许跳转到使用 alloca 创建的对象的范围内,而不是可变长度数组?我认为它们是等价的。

【问题讨论】:

挥手的答案是因为在 VLA 范围内进行分支会弄乱堆栈,但 sizeof(char*) 是固定的。我添加了律师标签,以增加您获得体面答案的机会。 谢谢,语言律师绝对适用。如果我不得不猜测,那就是在跳过alloca 之后使用s 已经是UB,因为s 将是一个未初始化的指针,但是在跳过VLA 声明之后使用s 是可以的,如果跳跃本身并没有被禁止。 alloca 不在标准中,因此编译器可能会将其视为任何其他函数(尽管它们是often don't),在这种情况下,根据标准一切都很好。不过,它当然可能会导致类似的未定义行为 @Kninnug:这很重要。 OP,您可以在没有该功能的情况下重新提出问题吗? 我忘记了alloca 不是标准函数。这个问题实际上是关于 VLA:s 和 alloca 之间的对比。仅 VLA 的问题已经存在(此处)[***.com/questions/20654191/…. 【参考方案1】:

这是因为编译器必须使用 VLA 运行时初始化作用域的框架。换句话说,您告诉它跳转到地址:END,但您要求它跳过该范围的框架的初始化代码。

为 VLA 初始化空间的代码就在计算 VLA 长度的表达式之前。如果您跳过某些 goto 可以执行的代码,则所有程序都会出现段错误。

想象一下:

if (cond) goto end;
...
char a[expr];
end:
a[i] = 20;

在这种情况下,代码将简单地出现段错误,因为您跳转到 VLA a 的 mutator 但 a 未初始化。初始化 VLA 的代码必须插入到定义的位置。

现在关于alloca。编译器也会这样做,但它无法检测到段错误。

所以这会出现段错误,编译器部分不会发出警告/错误。

逻辑与VLA相同。

int main(int argc, char *argv[])

    goto end;
    char *s = alloca(100);
end:
    s[1] = 2;
    return 0;

在 ISO 9899 中,这就是他们插入声明的原因:

6.8.6.1 goto 语句——约束

1 goto 语句中的标识符应命名位于的标签 在封闭函数的某个地方。 goto 语句不能跳转 从具有可变修改的标识符范围之外 在该标识符的范围内键入。

编译器在静态分析期间无法检测到此问题的正确答案,因为这实际上是halting problem

【讨论】:

所以没有技术原因(alloca 也可能是 malloc)禁止它,只是 C 标准试图使 VLA 适合静态分析,可能在它们是静态声明的基础(但考虑到它们的动态大小,它们无论如何都不能完全接受它)。在我看来,标准化 alloca 而不是 VLA 会是一个更好的决定。 @PSkocik 在我看来,语言定义所施加的这种限制是避免halting-problembrute-force 方式。【参考方案2】:

如果程序在声明后跳转,除了释放 VLA 的问题之外,sizeof 也存在问题。

想象一下你的程序是这样扩展的:

end:
    printf("size of str: %zu\n", sizeof s);
    return 0;

对于alloca 版本,sizeof s == sizeof(char*),可以在编译时计算,一切正常。但是,对于 VLA 版本,s 的长度是未知的,sizeof s 无法计算。

【讨论】:

您的观点很好,我只想补充一点,sizeof 的值实际上不是在 sizeof 调用的位置计算的,而是在声明的位置和sizeof 也存储在堆栈/某个寄存器的某个位置,因此在 sizeof 之前跳转并跳过定义位置的初始化代码会使 sizeof 打印一个巨大的,等等。 @alinsoar:您能否提供任何证据证明您声称 VLA 的大小是如何存储的? int a[m] ;米++;大小(一);大小是在定义时计算的,因此存储在某个地方。

以上是关于为啥我可以进入 alloca:d 变量的范围,而不是可变长度数组?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 dolphindb 脚本中的函数无法访问外部范围内的变量

为啥对象表达式中的代码可以从 kotlin 中包含它的范围访问变量?

为啥 Lambda 变量范围存在于 LINQ 查询之外?

为啥 evbuffer_add_printf 只接受静态变量而不接受“动态”变量?

将范围变量从控制器绑定到指令而不使用$ watch

PHP - 包含文件而不继承变量?