我正在处理一段遗留代码(没有测试)。我偶然发现了一个隐藏在几个宏中的部分。如果使用 GCC 的
-Wvla
. 编译,它会生成警告
问题代码相当于这个小程序中可以看到的内容:
typedef struct entry {
unsigned index;
unsigned reserved;
unsigned value;
} entry_t;
int main(int argc, char **argv) {
long pa = 0;
long res = pa + sizeof(entry_t[10 - argc]);
return res;
}
编译时,它给出警告:
$ gcc -g -Wvla repro-vla.c
repro-vla.c: In function ‘main’:
repro-vla.c:9:5: warning: ISO C90 forbids variable length array [-Wvla]
9 | long res = pa + sizeof(entry_t[10 - argc]);
| ^~~~
罪魁祸首当然是这个表情:
sizeof(entry_t[10 - argc])
。这里的语法有点混乱。我相信为10 - argc
类型的entry_t
条目创建了一个临时匿名数组,然后采用它的大小,并丢弃数组。
我的问题是:
sizeof(entry_t) * (10-argc)
有什么不同?两者都计算相同的值,并且都没有采取任何措施来防止下溢(当argc >= 10
时)。第二个表达式不使用可变长度数组,因此不会生成警告,而且在我看来也更容易理解。在此表达式中
sizeof(entry_t[10 - argc])
两个数组都没有创建。计算了表达式10 - argc
,并根据entry_t
的类型计算了这样一个数组的大小。 entry_t[10 - argc]
是类型说明符而不是表达式。所以例如你可能不会写
sizeof entry_t[10 - argc]
这些表达式
sizeof(entry_t[10 - argc])
和 sizeof(entry_t) * (10-argc)
产生相同的值,因为数组的大小等于数组中元素的数量乘以其元素的大小。
相对于 VLA 数组中的元素数量,C 标准中有一个限制,即 “每次评估时,它的值都应大于零。”
注意在这个表达式
sizeof(entry_t) * (10-argc)
中,当size_t
由于通常的算术转换而大于argc
时,您可以获得类型10
的非常大的值。
还有这条线
long res = pa + sizeof(entry_t[10 - argc]);
产生一个问题,为什么无符号整数类型
size_t
的值赋值给有符号整数类型long
的变量。
采用可变长度数组的 sizeof——这样做有什么好处吗?
我没有看到任何好处。我看到了替代代码的好处。
sizeof(entry_t) * (10-argc)
在 argc >= 10
时有一个定义的乘积*1。
sizeof(entry_t[10 - argc])
是 undefined behavior (UB) 在这种情况下,因为它不满足“如果大小是一个表达式,它不是一个整数常量表达式:......每次评估它都应该有一个大于零的值” C17dr § 6.7.6.2 5(数组声明符)
接下来的代码步骤,转换为
long
,然后转换为 int
,当 sizeof(entry_t) * (10-argc)
大于 LONG_MAX, INT_MAX
时,它们的实现定义了行为。
*1 确定键入
size_t
.