这个功能宏安全吗?

时间:2013-12-27 01:46:29

标签: c macros

你能否告诉我这个C“功能宏”是否有什么问题?

#define foo(P,I,X) do { (P)[I] = X; } while(0)

我的目标是,对于任何POD数据类型foofoofunc的行为与以下函数T完全相同(即intfloat*,{{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)的解决方案。

2 个答案:

答案 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)构造可以保护您的结果免受任何伤害,您的输入PI分别受()[]保护。什么不受保护,是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)