C语言中的函数宏定义

7
我想定义一个类似于宏的函数,例如:
#define foo(x)\
#if x>32\
 x\
#else\
 (2*x)\
#endif

也就是说,
if x>32, then foo(x) present x
else, foo(x) present (2*x)

但我的GCC报错了:

int a = foo(31);

我认为 C 预处理器应该正确处理这个问题,因为在编译时,它知道 x=33。它可以将 foo(33) 替换为 (2*33)

1
我可以问一下为什么你需要一个宏吗?使用函数会更加简洁和类型更安全。 - Michael Dorgan
如果您对此问题知之甚少,那么您应该忘记#define foo(x)这种构造存在,因为它只会帮助您编写可能仅仅凭借偶然性能够正常工作的代码。 - msw
1
我想让C预处理器进行评估,而不是运行时流程控制。这样可以在运行时节省时间。 - richard
5个回答

14

你可以按照以下步骤进行:

#define foo(x) ((x) > 32 ? (x) : (2 * (x)))

但是那种方法会多次评估x。您可以创建一个静态函数,这样更干净。

static int foo(int x) {
  if(x > 32) 
    return x;
  return 2 * x;
}
然后您也能够将具有副作用的内容传递给foo,并使副作用仅发生一次。
您编写的代码使用了#if#else#endif预处理器指令,但是如果您想要评估它们的值,那么您需要使用语言结构来传递变量到宏中。与实际语言结构中的ifelse语句不同,因为控制流语句不能评估值。换句话说,if语句仅用于控制流程(“如果A,则执行B,否则执行C”),而不会评估任何值。

在这两种情况下,x肯定会被评估两次吧? - anon
1
@Neil 在函数的情况下,参数(而不是形参)只会被计算一次。尝试调用 int i = 0; foo(i++); 对于函数的情况,i 在之后将会是 1。但对于宏的情况,它将会是 2 - Johannes Schaub - litb
我希望C预处理器在运行时控制流之前进行求值。这样可以节省运行时间。 顺便问一下,你为什么说“#define foo(x)((x)> 32?(x):(2 *(x)))会对x进行多次求值”? - richard
3
请将以下内容翻译成中文:@richard:将函数设为内联函数——如果在编译时已知值,则编译器通常会在编译时执行评估。即使对于没有标记为“inline”的静态函数,编译器也可能这样做。使用更现代的编译器/链接器工具,您甚至可以获得“extern”函数的优化效果(但可能性会降低)。通过VS2008进行快速测试显示,即使litb的“foo()”函数是在单独的源文件中定义的“extern”,它也没有问题地内联了该函数。当传递文字整数时,结果在编译时计算。 - Michael Burr
3
@richard: (继续说)- 使用现今的工具,其实没有必要担心你提到的“优化”问题。通常情况下,你不需要通过宏或预处理器技巧来“在运行时节省时间”。首先以正确、可维护、使用高效算法的方式编写代码(注意这与试图计算循环次数不同)。只有在初始尝试不够快速时才需要担心跳跃式优化。 - Michael Burr

11
#define \
    foo(x) \
    ({ \
        int xx = (x); \
        int result = (xx > 32) ? xx : (2*xx); \
        result; \
    })

1
@Michael:当然!在带有gcc的Ubuntu 10.04上。Linux内核宏通常也会这样做。我在我的Linux C++项目中广泛使用它。你觉得我的代码片段有什么奇怪的地方吗? - Vanni Totaro
1
@Michael:在 Linux 源代码树上尝试 grep -C2 -rn '})' . | grep -v Documentation | less,你会发现这种技术在很多地方都被使用了 :-) - Vanni Totaro
3
@Vanni: 我曾经忘记GCC有一个叫做“语句表达式”的扩展。由于提问者使用了 gcc 标签,这对他来说可能是可行的。但是,我认为一个简单的内联函数也可以起到同样的作用,而且更具可移植性。 - Michael Burr
2
@Michael:感谢您指出这是gcc扩展!我不知道它的名称和它不可移植性! :) - Vanni Totaro

4
int a = foo(31);

扩展为

int a = if 31>32
31
else
(2*31)
endif;

这就是C宏的工作方式,通过简单、愚蠢的替换来实现。如果你期望gcc能以更复杂、更智能的方式处理它们,那么你的期望是错误的。

鉴于此,很容易看出为什么你的代码无法正常工作。一个适用于此示例的替代方案如下:

#define foo(x) (x > 32 ? x : 2*x)

另一方面,我质疑宏是否真的是开始处理此类事情的适当工具。只需将其放入函数中,如果编译器认为可以加速代码,则会内联该代码。


1
我的答案中宏定义中的x应该像Johannes的宏定义一样用括号括起来。否则,对于复杂表达式,它将失败。这是避免使用宏定义的另一个原因 - 简单而无害的错误可能会导致非常奇怪的错误。 - Thom Smith

3

请考虑:

int x = rand()
int y = foo( x );

x在编译时未知。


编译器可以确定一个表达式是否为常量(例如,GCC有__builtin_constant_p),但是ISO C预处理器无法确定。 - yyny

2
问题不在于理论:假设你出于某种原因想要一个宏根据传递给它的参数值以不同的方式扩展,并且这个参数是已知于宏预处理器的常量,对于一个通用的宏处理器来说,没有理由它不能工作...但是cpp不幸地不允许其他宏处理器命令存在于宏定义中...
所以你的

#define foo(x) \
#if x>32 \
  x      \
#else    \
  2*x    \
#endif

不会扩展到

#if X>32
  X
#else
  2*X
#endif

其中X是已知参数(因此将X更改为例如31),这需要预处理器再次执行。

此外,换行符会被忽略,而它们对于这种用途非常重要;否则,以下内容可能被视为一个技巧(但需要另一次预处理通道)。

#define foo(x,y) \
y if x>32 \
  x  \
y else \ 
  2*x \
y endif

使用foo(20,#)将会产生

# if 20>32 20 # else 2*20 # endif

如果它能够工作,那就好了

# if 20>32
  20
# else
  2*20
# endif

...但是它并不是(正如所说,预处理器的输出必须再次馈送给预处理器...)

所以我的答案是,如果你需要这些东西,你不能使用C预处理器;你应该使用一个不常见的(非标准?)C预处理器,或者只是另一个宏处理器,如果你需要像"cpp"那样与C"集成"的那种东西,那么你不能轻易地使用通用的(像M4这样的)...


问题不在于理论:只要您出于某种原因想要拥有一个宏,根据传递给它的参数值以不同方式展开,并且此参数是已知于宏预处理器的常量。 是的,你理解我的意思。但解决方案非常棘手。还有其他方法吗? - richard
不更改宏(预)处理器的情况下,目前我不知道有类似于cpp并具备所需功能的宏处理器。 - ShinTakezou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接