何时以及如何在 sizeof 表达式中评估 VLA?
When and how are VLAs evaluated in sizeof expressions?
C 标准有这种语言:
6.5.3.4 The sizeof and _Alignof operators
Semantics
- The
sizeof
operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.
我不清楚标准的含义如果操作数的类型是可变长度数组类型,则计算操作数
- 如果操作数的类型是可变长度数组类型,那么评估参数似乎没有任何作用,因为大小可以从类型的定义中确定,正如[=中规定的那样27=]6.7.6.2 数组声明符 可变长度数组类型的每个实例的大小在其生命周期内不会改变。
- 另一方面,如果操作数是可变长度数组类型的括号名称,例如
sizeof(char[foo()])
,则必须在运行时评估大小表达式以计算大小,但是标准似乎没有涵盖这种情况(类型名称的类型是什么?)
是否应该修改 C 标准的语言以进行澄清?
这是一个测试程序,用于说明 VLA 在某些特定情况下的行为:
#include <stdio.h>
static int N = 0;
int foo(void) { return ++N; }
int main() {
typedef char S[foo()]; // foo() is called
printf("typedef char S[foo()];\t"); printf("N=%d\n", N);
printf("sizeof(S)=%d\t\t", (int)sizeof(S)); printf("N=%d\n", N);
typedef char U[foo()]; // foo() is called
printf("typedef char U[foo()];\t"); printf("N=%d\n", N);
printf("sizeof(U)=%d\t\t", (int)sizeof(U)); printf("N=%d\n", N);
S s1;
printf("S s1;\t\t\t"); printf("N=%d\n", N);
printf("sizeof(s1)=%d\t\t", (int)sizeof(s1)); printf("N=%d\n", N);
S s2;
printf("S s2;\t\t\t"); printf("N=%d\n", N);
printf("sizeof(s2)=%d\t\t", (int)sizeof(s2)); printf("N=%d\n", N);
U u1;
printf("U u1;\t\t\t"); printf("N=%d\n", N);
printf("sizeof(u1)=%d\t\t", (int)sizeof(u1)); printf("N=%d\n", N);
U *pu1 = &u1;
printf("U *pu1 = &u1;\t\t"); printf("N=%d\n", N);
printf("sizeof(*pu1)=%d\t\t", (int)sizeof(*pu1)); printf("N=%d\n", N);
U *pu2 = NULL;
printf("U *pu2 = NULL;\t\t"); printf("N=%d\n", N);
// sizeof(*pu2) does not evaluate *pu2, contrary to the Standard specification
printf("sizeof(*pu2)=%d\t\t", (int)sizeof(*pu2)); printf("N=%d\n", N);
char x2[foo()][foo()]; // foo() is called twice
printf("char x2[foo()][foo()];\t"); printf("N=%d\n", N);
printf("sizeof(x2)=%d\t\t", (int)sizeof(x2)); printf("N=%d\n", N);
printf("sizeof(x2[0])=%d\t\t", (int)sizeof(x2[0])); printf("N=%d\n", N);
// sizeof(char[foo()]) evaluates foo()
printf("sizeof(char[foo()])=%d\t", (int)sizeof(char[foo()])); printf("N=%d\n", N);
return 0;
}
输出(clang 和 gcc):
typedef char S[foo()]; N=1
sizeof(S)=1 N=1
typedef char U[foo()]; N=2
sizeof(U)=2 N=2
S s1; N=2
sizeof(s1)=1 N=2
S s2; N=2
sizeof(s2)=1 N=2
U u1; N=2
sizeof(u1)=2 N=2
U *pu1 = &u1; N=2
sizeof(*pu1)=2 N=2
U *pu2 = NULL; N=2
sizeof(*pu2)=2 N=2
char x2[foo()][foo()]; N=4
sizeof(x2)=12 N=4
sizeof(x2[0])=4 N=4
sizeof(char[foo()])=5 N=5
每个可变修改类型都有一个大小,对于每个维度,该大小要么是该维度的倍数,要么独立于它。没有理由评估可变修改对象的大小应该要求评估不会影响对象大小的任何维度的值,但一些编译器可能会评估此类维度的值,因为可变修改类型的原始规则暗示他们应该被评估。在不同的实现以不同方式处理构造的情况下,标准的作者倾向于避免让标准建议任何一种行为都更好。因此,该标准故意对涉及可变修改类型的极端情况含糊不清,以避免必须将任何现有实现的行为描述为“错误”或劣质。
If the type of the operand is a variable length array type, it does not seem to serve any purpose to evaluate the argument as the size can be determined from the definition of the type, as it is stipulated in 6.7.6.2 Array declarators that The size of each instance of a variable length array type does not change during its lifetime.
但是直到数组在运行时实例化,该大小才为人所知。某种 的评估必须 在运行时执行。未指定评估的具体内容。
Should the language of the C Standard be amended for clarification?
我想是的,是的。我认为以下习惯用法对于动态分配行数和列数直到运行时才知道的二维数组非常有用:
int rows, cols;
...
T (*arr)[cols] = malloc( sizeof *arr * rows );
但是,按照标准当前的措辞,这(很可能)会调用未定义的行为,因为我在运行时评估 *arr
,但 arr
在那一点。您不需要 取消引用 arr
来获取数组类型的大小,但不幸的是,标准中的语言并不是那么精细。我希望看到类似于“如果操作数的类型是可变长度数组类型,则计算操作数 以单独获取数组大小的目的 ” 的语言。
C 标准有这种语言:
6.5.3.4 The sizeof and _Alignof operators
Semantics
- The
sizeof
operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.
我不清楚标准的含义如果操作数的类型是可变长度数组类型,则计算操作数
- 如果操作数的类型是可变长度数组类型,那么评估参数似乎没有任何作用,因为大小可以从类型的定义中确定,正如[=中规定的那样27=]6.7.6.2 数组声明符 可变长度数组类型的每个实例的大小在其生命周期内不会改变。
- 另一方面,如果操作数是可变长度数组类型的括号名称,例如
sizeof(char[foo()])
,则必须在运行时评估大小表达式以计算大小,但是标准似乎没有涵盖这种情况(类型名称的类型是什么?)
是否应该修改 C 标准的语言以进行澄清?
这是一个测试程序,用于说明 VLA 在某些特定情况下的行为:
#include <stdio.h>
static int N = 0;
int foo(void) { return ++N; }
int main() {
typedef char S[foo()]; // foo() is called
printf("typedef char S[foo()];\t"); printf("N=%d\n", N);
printf("sizeof(S)=%d\t\t", (int)sizeof(S)); printf("N=%d\n", N);
typedef char U[foo()]; // foo() is called
printf("typedef char U[foo()];\t"); printf("N=%d\n", N);
printf("sizeof(U)=%d\t\t", (int)sizeof(U)); printf("N=%d\n", N);
S s1;
printf("S s1;\t\t\t"); printf("N=%d\n", N);
printf("sizeof(s1)=%d\t\t", (int)sizeof(s1)); printf("N=%d\n", N);
S s2;
printf("S s2;\t\t\t"); printf("N=%d\n", N);
printf("sizeof(s2)=%d\t\t", (int)sizeof(s2)); printf("N=%d\n", N);
U u1;
printf("U u1;\t\t\t"); printf("N=%d\n", N);
printf("sizeof(u1)=%d\t\t", (int)sizeof(u1)); printf("N=%d\n", N);
U *pu1 = &u1;
printf("U *pu1 = &u1;\t\t"); printf("N=%d\n", N);
printf("sizeof(*pu1)=%d\t\t", (int)sizeof(*pu1)); printf("N=%d\n", N);
U *pu2 = NULL;
printf("U *pu2 = NULL;\t\t"); printf("N=%d\n", N);
// sizeof(*pu2) does not evaluate *pu2, contrary to the Standard specification
printf("sizeof(*pu2)=%d\t\t", (int)sizeof(*pu2)); printf("N=%d\n", N);
char x2[foo()][foo()]; // foo() is called twice
printf("char x2[foo()][foo()];\t"); printf("N=%d\n", N);
printf("sizeof(x2)=%d\t\t", (int)sizeof(x2)); printf("N=%d\n", N);
printf("sizeof(x2[0])=%d\t\t", (int)sizeof(x2[0])); printf("N=%d\n", N);
// sizeof(char[foo()]) evaluates foo()
printf("sizeof(char[foo()])=%d\t", (int)sizeof(char[foo()])); printf("N=%d\n", N);
return 0;
}
输出(clang 和 gcc):
typedef char S[foo()]; N=1
sizeof(S)=1 N=1
typedef char U[foo()]; N=2
sizeof(U)=2 N=2
S s1; N=2
sizeof(s1)=1 N=2
S s2; N=2
sizeof(s2)=1 N=2
U u1; N=2
sizeof(u1)=2 N=2
U *pu1 = &u1; N=2
sizeof(*pu1)=2 N=2
U *pu2 = NULL; N=2
sizeof(*pu2)=2 N=2
char x2[foo()][foo()]; N=4
sizeof(x2)=12 N=4
sizeof(x2[0])=4 N=4
sizeof(char[foo()])=5 N=5
每个可变修改类型都有一个大小,对于每个维度,该大小要么是该维度的倍数,要么独立于它。没有理由评估可变修改对象的大小应该要求评估不会影响对象大小的任何维度的值,但一些编译器可能会评估此类维度的值,因为可变修改类型的原始规则暗示他们应该被评估。在不同的实现以不同方式处理构造的情况下,标准的作者倾向于避免让标准建议任何一种行为都更好。因此,该标准故意对涉及可变修改类型的极端情况含糊不清,以避免必须将任何现有实现的行为描述为“错误”或劣质。
If the type of the operand is a variable length array type, it does not seem to serve any purpose to evaluate the argument as the size can be determined from the definition of the type, as it is stipulated in 6.7.6.2 Array declarators that The size of each instance of a variable length array type does not change during its lifetime.
但是直到数组在运行时实例化,该大小才为人所知。某种 的评估必须 在运行时执行。未指定评估的具体内容。
Should the language of the C Standard be amended for clarification?
我想是的,是的。我认为以下习惯用法对于动态分配行数和列数直到运行时才知道的二维数组非常有用:
int rows, cols;
...
T (*arr)[cols] = malloc( sizeof *arr * rows );
但是,按照标准当前的措辞,这(很可能)会调用未定义的行为,因为我在运行时评估 *arr
,但 arr
在那一点。您不需要 取消引用 arr
来获取数组类型的大小,但不幸的是,标准中的语言并不是那么精细。我希望看到类似于“如果操作数的类型是可变长度数组类型,则计算操作数 以单独获取数组大小的目的 ” 的语言。