为啥我可以进入 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-problem
的brute-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 中包含它的范围访问变量?