【问题标题】:Macro-like function in c [duplicate]c中的类似宏的函数
【发布时间】:2020-01-09 16:50:46
【问题描述】:

我有一些带有宏功能的 c 代码,但我无法理解它的输出,

#define COMP(A,B) ( ((A) * (A)) + ((B) * (B)) )
int main()
{
int x = 2, y= 3, z;
z= COMP(x++,++y);
printf("x = %d\n", x);
printf("y = %d\n", y);
printf("z = %d\n", z);
}

不同编译器的输出是 4 5 31,有人能说明原因吗?

【问题讨论】:

  • @JL2210 可能不值得重新打开,因为我已经回答过了,但是为了记录,我不相信“显而易见”的问题,Why are these constructs using pre and post-increment undefined behavior?,是一个适当的重复,因为有一个这个特定问题的重要额外难题,即宏扩展引入了未定义的行为。 (我怀疑还有一个副本涵盖了这方面,但我不知道它是什么。)
  • 我现在找到并添加了更好的副本。

标签: c c-preprocessor


【解决方案1】:

代码是未定义。

宏调用COMP(x++, ++y) 扩展为

( ((x++) * (x++)) + ((++y) * (++y)) )

当我们有一个表达式或子表达式时

x++ * x++

当同一个变量x 被修改两次时,没有规则说哪个修改首先发生。 (在你问之前:不,括号不会改变任何东西,优先级也不会。)因此,编译器不必以一种或另一种方式来做,这就是使代码未定义的原因:编译器不'根本不需要做任何合理的事情,因为你不应该首先编写未定义的代码。

有关完整故事,请参阅规范问题Why are these constructs using pre and post-increment undefined behavior?

这就是为什么必须小心使用COMP() 等“类函数”宏的原因之一,或者根本不使用。基本上有三个规则:

  1. 将宏定义中的所有参数括起来
  2. 将整个定义用括号括起来
  3. 一般来说,不要在有副作用的参数上调用宏

COMP 宏的定义遵循规则 1 和 2,但调用 COMP(x++, ++y) 显然违反了规则 3。

另请参阅旧的C FAQ list 中的问题question 10.1。

对于大多数(如果不是全部)类函数宏的使用,内联函数是更可取的。如果你写了

inline int comp(int x, int y) { return x*x + y*y; }

我认为你会得到一个更明智的结果。内联函数需要像常规函数调用一样评估它们的参数,因此在这种情况下,当您调用 comp(x++, ++y) 时,您只会得到一个 x++ 和一个 ++y,这意味着没有未定义的行为,并且是明确定义的结果.


作为另一个可能的兴趣点,我已经看到这个问题的一个版本出现在生产代码中,在一种令人惊讶的自然、非人为的情况下。想象一下,您正在编写一个基于堆栈的小型算术评估器。暂时忽略堆栈溢出检测,您可能会从类似的东西开始

int stack[50];
int *stackp = stack;

#define Push(val) (*stackp++ = (val))
#define Pop() (*--stackp)

然后你可以使用类似的代码将一些数字压入堆栈

Push(10);
Push(20);

然后您可以实现算术运算符,将数字从堆栈中弹出,执行操作并推送结果。这是补充:

int x = Pop();
int y = Pop();
Push(x + y);

但您几乎立即意识到,谁需要这些临时变量?然后您将其“简化”为

Push(Pop() + Pop());

但这不起作用,它会给你奇怪的结果,原因是它扩展为

(*stackp++ = ((*--stackp) + (*--stackp)));

这是 mondo 未定义的。 (我记得在 2003 年左右帮助一位同事在他的代码中追踪这个确切的问题。)

【讨论】:

    猜你喜欢
    • 2022-11-24
    • 1970-01-01
    • 1970-01-01
    • 2014-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-08
    相关资源
    最近更新 更多