C语言 使用可变长度数组的sizeof-这样做有什么好处吗?

tyky79it  于 2023-03-01  发布在  其他
关注(0)|答案(3)|浏览(184)

我正在处理一段遗留代码(没有测试)。我偶然发现了一个隐藏在几个宏中的部分。如果用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]).这里的语法有点混乱,我相信为entry_t类型的10 - argc条目创建了一个临时匿名数组,然后取其大小,并丢弃该数组。
我的问题是:
1.我对现在编写的代码的理解是否正确?
1.这个表达式与sizeof(entry_t) * (10-argc)有什么不同?两者计算相同的值,并且都没有做任何事情来防止下溢(当argc >= 10时)。第二个表达式不使用可变长度数组,因此不会生成警告,在我看来,它也更容易理解。

zc0qhyus

zc0qhyus1#

不创建临时数组对象。
有一种常见的误解,认为VLA是关于运行时定义的长度的堆栈分配数组,这是一种更安全的alloca()形式。
不。VLA是关于键入而不是存储的。下面一行是“VLA特性”的本质:

typedef int T[n];

非:

int A[n];

注意,VLA type 声明不分配任何存储空间,并且可以使用而不用担心堆栈溢出。VLA类型对于处理多维数组和在函数参数中表示访问范围(例如void foo(int n, int arr[n]);)非常有用。VLA类型在C11中是可选的,但由于其有用性,在C23中将是强制性的。
表达式sizeof(entry_t[10 - argc]基本上与下式相同:

typedef entry_t _unnamed_type[10 - argc];
sizeof(_unnamed_type)

这里没有创建VLA数组对象。我认为问题出在-Wvla标志本身。-Wvla警告任何 *VLA类型 * 的声明(不是VLA对象)什么是过分的,因为它也捕捉VLA类型的良好用法。有一个request添加-Wvla-stack-allocation警告到clang以捕捉VLA的危险用法。或者,可以使用gcc的-Wvla-larger-than=0,但是它工作得不是很好。

am46iovg

am46iovg2#

使用可变长度数组的sizeof-这样做有什么好处吗?

    • 具有替代代码的福利**

argc >= 10时,sizeof(entry_t) * (10-argc)具有定义的乘积 * 1。在这种情况下,sizeof(entry_t[10 - argc])是 * 未定义的行为 *(UB),因为它不能满足"如果大小是不是整数常量表达式的表达式:......每次求值时,它的值都应该大于零。"C17dr § www.example.com 5(数组声明符)6.7.6.2 5 (Array declarators)
接下来的代码步骤(转换为long,然后转换为int)在sizeof(entry_t) * (10-argc)大于LONG_MAX, INT_MAX时具有其实现定义的行为。

    • 小优势**

给定UB为argc >= 10,优化编译器可以假设argc < 10,并且发射代码利用返回值将仅在窄范围内的优势(例如:[3*4 ... 3*4*9])-因此只需要使用狭义的整数数学。没有太大的优势-但它就在那里。

  • 1当然要输入size_t
htrmnn0y

htrmnn0y3#

在这个表达式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)产生相同的值,因为数组的大小等于数组中元素的数量乘以其元素的大小,假定10大于argc
关于VLA中的元素数量,C标准中有一个限制,即 “每次评估时,它的值都应大于零。"
请注意,在表达式sizeof(entry_t) * (10-argc)中,当argc大于10时,由于通常的算术转换,您可以获得size_t类型的非常大的值。
还有这条线

long res = pa + sizeof(entry_t[10 - argc]);

提出了为什么将无符号整数类型size_t的值赋给有符号整数类型long的变量的问题。

相关问题