Min*_*ock 6 c arrays goto variable-declaration
我正在阅读C编程 - KNKing 的现代方法来学习C编程语言,并注意到goto语句不能跳过变长数组声明.
但现在的问题是:为什么goto跳转允许跳过固定长度的数组声明和普通声明?更准确地说,根据C99标准,这些例子的行为是什么?当我测试这些案例时,似乎声明实际上没有被跳过,但这是正确的吗?声明可能已跳过的变量是否可以安全使用?
1.
goto later;
int a = 4;
later:
printf("%d", a);
Run Code Online (Sandbox Code Playgroud)
2.
goto later;
int a;
later:
a = 4;
printf("%d", a);
Run Code Online (Sandbox Code Playgroud)
3.
goto later;
int a[4];
a[0] = 1;
later:
a[1] = 2;
for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
printf("%d\n", a[i]);
Run Code Online (Sandbox Code Playgroud)
Min*_*s97 10
我有心情解释这个没有血腥的记忆布局细节(相信我,当使用VLA时它们会变得非常血腥;详见@ Ulfalizer的回答).
所以,最初,在C89中,必须在块的开头声明所有变量,如下所示:
{
int a = 1;
a++;
/* ... */
}
Run Code Online (Sandbox Code Playgroud)
这直接意味着一件非常重要的事情:一个块==一组不变的变量声明.
C99改变了这一点.在其中,您可以在块的任何部分声明变量,但声明语句仍然与常规语句不同.
实际上,为了理解这一点,您可以想象所有变量声明都被隐式移动到声明它们的块的开头,并且对于它们之前的所有语句都不可用.
这只是因为一个块==一组声明规则仍然成立.
这就是为什么你不能"跳过声明".声明的变量仍然存在.
问题是初始化.它不会在任何地方"移动".因此,从技术上讲,对于您的情况,以下程序可视为等效:
goto later;
int a = 100;
later:
printf("%d", a);
Run Code Online (Sandbox Code Playgroud)
和
int a;
goto later;
a = 100;
later:
printf("%d", a);
Run Code Online (Sandbox Code Playgroud)
如您所见,声明仍然存在,正在跳过的是初始化.
这与VGA不兼容的原因是它们不同.简而言之,这是因为这是有效的:
int size = 7;
int test[size];
Run Code Online (Sandbox Code Playgroud)
与所有其他声明不同,VLA的声明在声明它们的块的不同部分中表现不同.实际上,VLA可能具有完全不同的内存布局,具体取决于它的声明位置.你不能将它"移动"到你跳过的地方之外.
你可能会问,"好吧,为什么不这样做,以便宣言不受goto"?好吧,你仍然会得到这样的案例:
goto later;
int size = 7;
int test[size];
later:
Run Code Online (Sandbox Code Playgroud)
你真的希望这件事做什么?
因此,禁止跳过VLA声明是有原因的 - 通过简单地完全禁止它们来处理上述情况是最合乎逻辑的决定.
你不允许跳过可变长度数组(VLA)的声明的原因是它会使VLAs通常被实现的方式变得混乱,并且会使语言的语义复杂化.
在实践中可能实现的VLA的方式是通过动态(在运行时计算)量减少(或递增,在堆栈向上增长的架构上)堆栈指针,以便为堆栈上的VLA腾出空间.这发生在声明VLA的位置(概念上至少,忽略优化).这是必需的,以便稍后的堆栈操作(例如,将参数推送到堆栈以进行函数调用)不会踩到VLA的内存.
对于嵌套在块中的VLA,堆栈指针通常在包含VLA的块的末尾被恢复.如果goto允许a 跳转到这样的块并超过VLA的声明,则恢复堆栈指针的代码将在没有运行相应的初始化代码的情况下运行,这可能会导致问题.例如,堆栈指针可能会增加VLA的大小,即使它从未递减过,除其他外,这将使得当调用包含VLA的函数时出现的返回地址出现在相对错误的位置到堆栈指针.
从纯语言语义的角度来看,它也很混乱.如果你被允许跳过声明,那么数组的大小是多少?应该sizeof返回什么?访问它意味着什么?
对于非VLA情况,您只需跳过值初始化(如果有的话),这不一定会导致问题本身.如果跳过非VLA定义int x;,那么仍然会为变量保留存储空间x.VLA的不同之处在于它们的大小是在运行时计算的,这使事情变得复杂.
作为旁注,允许变量在C99中的块内的任何地方声明的动机之一(C89要求声明在块的开头,尽管至少GCC允许它们在块中作为扩展)是支持沃拉斯.在声明VLA的大小之前能够在块中更早地执行计算是很方便的.
出于某些相关原因,C++不允许gotos跳过对象声明(或简单旧数据类型的初始化,例如int).这是因为跳过调用构造函数但仍然在块结尾处运行析构函数的代码是不安全的.