宏内的双重评估:sizeof() 的一个案例,用于确定作为复合文字传递的数组大小
Double evaluation within macro: a case of sizeof() to determine array's size passed as compound literal
C99 使得基本上可以在任何地方定义数组,作为复合文字。
例如,给定一个接受数组 float
作为输入的普通函数 sumf()
,我们期望原型为:
float sumf(const float* arrayf, size_t size);
然后可以这样使用:
float total = sumf( (const float[]){ f1, f2, f3 }, 3 );
这很方便,因为不需要事先声明一个变量。
语法有点难看,但这可以隐藏在宏后面。
但是,请注意最后的 3
。这是数组的大小。这是必需的,以便 sumf()
知道在哪里停止。但随着代码老化和重构,它也很容易出错,因为现在第二个参数必须与第一个参数定义保持同步。例如,添加f4
需要将此值更新为4,否则函数return计算错误(并且没有警告通知此问题)。
所以最好让两者保持同步。
如果是通过变量声明的数组,就容易了。
我们可以有一个宏,它可以简化这样的表达式:float total = sumf( ARRAY(array_f) );
只用 #define ARRAY(a) (a) , sizeof(a) / sizeof(*(a))
。但是, array_f
必须在调用函数之前定义,所以它不再是复合文字。
因为是复合字面量,所以没有名字,所以无法引用。因此我找不到比在两个参数中重复复合文字更好的方法了。
#define LIST_F(...) (const float*)( __VA_ARGS__) , sizeof((const float*)( __VA_ARGS__)) / sizeof(float)
float total = sumf ( LIST_F( f1, f2, f3 ) );
这会奏效。在列表中添加 f4
会自动将 size
参数更新为正确的大小。
但是,只要所有成员都是变量,这一切都可以正常工作。但是它是一个函数的情况呢?该函数会被调用两次吗?
比如说:float total = sumf ( LIST_F( v1, f2() ) );
,f2()
会被调用两次吗?这对我来说不清楚,因为 sizeof()
中提到了 f2()
,因此理论上它可以在不实际调用 f2()
的情况下知道 return 类型大小。但我不确定标准是怎么说的。有保证吗 ?它取决于实现吗?
will f2() be invoked twice?
不,sizeof
不求值(除非是变长数组,但不是)。
what the standard says about that. Is there a guarantee ?
The sizeof operator yields the size (in bytes) of its operand, [...] 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.
Is it implementation dependent ?
不,应该总是好的。
请注意,您的其他宏使用 (const float*)(__VA_ARGS__)
,这将不起作用 - 语法为 (float[]){ stuff }
。无论如何,我只会做一个宏,为什么要做两个,打字太多了。只是:
#define SUMF_ARRAY(...) \
sumf( \
(const float[]){__VA_ARGS__}, \
sizeof((const float[]){__VA_ARGS__}) / sizeof(float))
float total = SUMF_ARRAY(f1(), f2(), f3());
C99 使得基本上可以在任何地方定义数组,作为复合文字。
例如,给定一个接受数组 float
作为输入的普通函数 sumf()
,我们期望原型为:
float sumf(const float* arrayf, size_t size);
然后可以这样使用:
float total = sumf( (const float[]){ f1, f2, f3 }, 3 );
这很方便,因为不需要事先声明一个变量。 语法有点难看,但这可以隐藏在宏后面。
但是,请注意最后的 3
。这是数组的大小。这是必需的,以便 sumf()
知道在哪里停止。但随着代码老化和重构,它也很容易出错,因为现在第二个参数必须与第一个参数定义保持同步。例如,添加f4
需要将此值更新为4,否则函数return计算错误(并且没有警告通知此问题)。
所以最好让两者保持同步。
如果是通过变量声明的数组,就容易了。
我们可以有一个宏,它可以简化这样的表达式:float total = sumf( ARRAY(array_f) );
只用 #define ARRAY(a) (a) , sizeof(a) / sizeof(*(a))
。但是, array_f
必须在调用函数之前定义,所以它不再是复合文字。
因为是复合字面量,所以没有名字,所以无法引用。因此我找不到比在两个参数中重复复合文字更好的方法了。
#define LIST_F(...) (const float*)( __VA_ARGS__) , sizeof((const float*)( __VA_ARGS__)) / sizeof(float)
float total = sumf ( LIST_F( f1, f2, f3 ) );
这会奏效。在列表中添加 f4
会自动将 size
参数更新为正确的大小。
但是,只要所有成员都是变量,这一切都可以正常工作。但是它是一个函数的情况呢?该函数会被调用两次吗?
比如说:float total = sumf ( LIST_F( v1, f2() ) );
,f2()
会被调用两次吗?这对我来说不清楚,因为 sizeof()
中提到了 f2()
,因此理论上它可以在不实际调用 f2()
的情况下知道 return 类型大小。但我不确定标准是怎么说的。有保证吗 ?它取决于实现吗?
will f2() be invoked twice?
不,sizeof
不求值(除非是变长数组,但不是)。
what the standard says about that. Is there a guarantee ?
The sizeof operator yields the size (in bytes) of its operand, [...] 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.
Is it implementation dependent ?
不,应该总是好的。
请注意,您的其他宏使用 (const float*)(__VA_ARGS__)
,这将不起作用 - 语法为 (float[]){ stuff }
。无论如何,我只会做一个宏,为什么要做两个,打字太多了。只是:
#define SUMF_ARRAY(...) \
sumf( \
(const float[]){__VA_ARGS__}, \
sizeof((const float[]){__VA_ARGS__}) / sizeof(float))
float total = SUMF_ARRAY(f1(), f2(), f3());