已经有两个答案引用了N1570 6.5.3.4p2:
sizeof
操作符返回其操作数的大小(以字节为单位),操作数可以是表达式或类型名括号。大小取决于操作数的类型。结果是一个整数。如果操作数的类型是可变长度数组类型,则对操作数进行评估;否则,不评估操作数并且结果为整数常量。
根据标准中的这段代码,是的,sizeof
的操作数会被评估。
我将争辩说,这在标准中是一个缺陷; 在运行时会评估某些内容,但不会评估操作数。
让我们考虑一个更简单的例子:
int len = 100;
double vla[len];
printf("sizeof vla = %zu\n", sizeof vla);
根据标准,sizeof vla
会计算表达式vla
的大小。但是这意味着什么?
在大多数情况下,评估数组表达式将产生初始元素的地址,但是sizeof
运算符是明确的例外。我们可能会认为评估vla
意味着访问其元素的值,但由于这些元素尚未初始化,因此具有未定义的行为。但是,在数组表达式的其他上下文中,没有任何其他情况涉及访问其元素的值,并且在这种情况下绝对不需要这样做。(更正:如果使用字符串字面值初始化数组对象,则会评估元素的值。)
当执行vla
的声明时,编译器将创建一些匿名元数据来保存数组的长度(必须这样做,因为在定义和分配了vla
后,将新值赋给len
不会更改vla
的长度)。只需通过sizeof (double)
乘以存储的值(或者仅检索存储的值,如果它以字节为单位存储大小)即可确定sizeof vla
。
sizeof
也可以应用于带括号的类型名称:
int len = 100;
printf("sizeof (double[len]) = %zu\n", sizeof (double[len]));
根据标准,
sizeof
表达式会计算出
类型。这是什么意思?显然它要计算出当前
len
的值。另一个例子:
根据标准,sizeof
表达式会计算出类型。这是什么意思?显然它要计算出当前len
的值。另一个例子:
size_t func(void);
printf("sizeof (double[func()]) = %zu\n", sizeof (double[func()]));
这里的类型名包含函数调用。评估sizeof
表达式必须调用该函数。
但在所有这些情况下,实际上没有必要评估数组对象的元素(如果有的话),也没有必要这样做。
sizeof
应用于除VLA之外的任何东西都可以在编译时评估。当sizeof
应用于VLA(对象或类型)时的区别在于,必须在运行时评估某些内容。但必须评估的是确定操作数大小所需的任何内容,而不是sizeof
的操作数本身。
标准规定,如果sizeof
的操作数是可变长度数组类型,则必须评估该操作数。这是标准中的缺陷。
回到问题中的示例:
int foo = 100;
double (*bar)[foo] = NULL;
printf("sizeof *bar = %zu\n", sizeof *bar);
我已经将对NULL
的初始化添加到代码中,以使解引用bar
具有未定义的行为变得更加清晰。
*bar
的类型为double [foo]
,这是一个VLA类型。原则上,应该计算*bar
的值,但由于bar
未初始化,因此会产生未定义的行为。但是,没有必要解引用bar
。编译器在处理double [foo]
类型时将生成一些代码,包括将foo
的值(或foo * sizeof(double)
)保存在匿名变量中。为了计算sizeof *bar
,它只需检索该匿名变量的值即可。如果标准更新以一致的方式定义sizeof
的语义,则可以清楚地了解评估sizeof *bar
是良好定义的,并且可以在不必解引用bar
的情况下生成100 * sizeof(double)
的结果。