【问题标题】:Is this function macro safe?这个函数宏安全吗?
【发布时间】:2013-12-27 01:46:29
【问题描述】:

你能告诉我这个C“函数宏”有什么问题吗?

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

我的目标是 foo 对于任何 POD 数据类型 T(即 intfloat*struct my_struct { int a,b,c; })的行为与以下函数 foofunc 完全相同:

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 的行为与函数 foofunc 不同的情况?

在 C++ 中,可以将 foofunc 定义为模板,并且不需要宏。但我正在寻找一种适用于普通 C (C99) 的解决方案。

【问题讨论】:

  • 出于好奇,当foofunc 将被内联时,为什么要使用宏?尽管使用指针存在风险,但不会有任何性能优势。
  • foofunc 仅适用于类型 T,因此每个类型需要一个定义。如果我想要 int、float* 和 my_struct 的 foofunc,我需要 foofunc_int、foofunc_floatptr、foofunc_my_struct。该宏适用于任何类型。
  • 这就是模板的用途。只需在你的函数定义之前加上template <typename T>,它就会像你想要的那样工作。
  • @parkovski:这个问题是关于 C,而不是 C++。所以没有模板:(
  • @OliCharlesworth 提到了一个很好的观点,其中奇怪的参数实际上被编译了。如果你用括号括起来,那应该可以解决所有可能的参数问题(至少是我能想象到的)(即你的宏是(P)[(I)] = (X))。

标签: c macros


【解决方案1】:

您的宏适用于任意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 的类型决定了哪个类型的 foofunc 是等效的。

在重要方面,宏是安全的。使用适当的括号,如果你传递语法上合理的参数,你会得到一个格式良好的展开。由于每个参数只使用一次,所有副作用都会发生。无论哪种方式,参数之间的评估顺序都是未定义的。

【讨论】:

  • 逗号表达的好点! int i; int*p=&i; if(foo(p,0,5), i==5) { /**/ } 仅适用于 foofunc,不适用于 foo
【解决方案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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-17
    • 1970-01-01
    • 1970-01-01
    • 2023-01-30
    • 2010-10-11
    • 1970-01-01
    相关资源
    最近更新 更多