你能否告诉我这个C“功能宏”是否有什么问题?
#define foo(P,I,X) do { (P)[I] = X; } while(0)
我的目标是,对于任何POD数据类型foo
,foofunc
的行为与以下函数T
完全相同(即int
,float*
,{{1 }}):
struct my_struct { int a,b,c; }
例如,这是正常的:
static inline void foofunc(T* p, size_t i, T x) { p[i] = x; }
由于将int i = 0;
float p;
foo(&p,i++,42.0f);
放在括号中,它可以处理&p
之类的内容,因为P
仅在宏中出现一次,因此它只会增加i
一次,并且需要由I
引起的行尾的分号。
还有其他我不知道的情况,以及宏do {} while(0)
的行为与foo
函数不同吗?
在C ++中,可以将foofunc定义为模板,不需要宏。但我寻找一种适用于普通C(C99)的解决方案。
答案 0 :(得分:2)
您的宏适用于任意X
参数的事实取决于运算符优先级的细节。我建议使用括号,即使这里没有必要使用括号。
#define foo(P,I,X) do { (P)[I] = (X); } while(0)
这是一个指令,而不是一个表达式,所以它不能在foofunc(P,I,X)
可能的任何地方使用。即使foofunc
返回void
,也可以在逗号表达式中使用它; foo
不能。但是,如果您不想冒险使用结果,则可以轻松地将foo
定义为表达式,并将其转换为void
。
#define foo(P,I,X) ((void)((P)[I] = (X)))
使用宏而不是函数,丢失的只是错误检查。例如,您可以编写foo(3, ptr, 42)
而不是foo(ptr, 3, 42)
。在size_t
小于ptrdiff_t
的实现中,使用该函数可能会截断I
,但宏的行为更直观。 X
的类型可能与P
指向的类型不同:将进行自动转换,因此实际上P
的类型决定了哪个类型{{1}等同。
在重要方面,宏是安全的。使用适当的括号,如果传递语法上合理的参数,则会得到格式良好的扩展。由于每个参数仅使用一次,因此会发生所有副作用。无论如何,参数之间的评估顺序是不确定的。
答案 1 :(得分:2)
do { ... } while(0)
构造可以保护您的结果免受任何伤害,您的输入P
和I
分别受()
和[]
保护。什么不受保护,是X
。所以问题是,X
是否需要保护。
查看运算符优先级表(http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence),我们看到只有两个运算符被列为优先级低于=
,因此赋值可以窃取它们的参数:throw
运算符(仅限C ++)和,
运算符。
现在,除了仅限C ++之外,throw
运算符是不加批判的,因为它没有可能被盗的左手参数。
另一方面,,
运算符如果X
可以将其包含为顶级运算符,则会出现问题。但是如果你解析语句
foo(array, index, x += y, y)
您会看到,
运算符将被解释为分隔第四个参数,并且
foo(array, index, (x += y, y))
已经附带了它所需的括号。
长话短说:
是的,你的定义是安全的。
但是,您的定义依赖于无法在不添加括号的情况下将stuff, more_stuff
作为一个宏参数传递。我宁愿不依赖这样的复杂性,只是写出明显安全的
#define foo(P, I, X) do { (P)[I] = (X); } while(0)