【问题标题】:C Macro Question -(x) vs (-x)C 宏问题 -(x) 与 (-x)
【发布时间】:2011-01-02 18:43:25
【问题描述】:

我正在查看教授的测验答案,一个问题是:

绝对值宏等函数的正确实现是:

#define abs(x) ((x)<0 ? (-x) : (x))
#define abs(x) ((x)<0 ? -(x) : (x))

为什么第二个比第一个正确?

还有为什么一定要用()。比如涉及的规则是什么?每个变量都需要一个()?谢谢。

【问题讨论】:

  • abs(1-2) 是什么?一般来说,对于括号,如果我们定义一个宏#define sqr(x) x*x,那么sqr(1+2)会给出错误的答案,因为它扩展为1+2*1+2,而不是(1+2)*(1+2)
  • Alok:你应该写一个答案!这条评论是正确的。
  • 正确答案是 c) 以上都不是……不要为此使用宏!当您尝试abs(x++) 时会发生什么?
  • 是的,所以abs() 宏应该带有免责声明:“可能多次评估参数”,就像getc()getc 函数等同于@987654331 @,除非它被实现为宏,它可能会多次计算流,因此参数永远不应是具有副作用的表达式。

标签: c macros


【解决方案1】:

是的,每个变量都需要直接用括号括起来。

原因是因为您可以将不“好”的东西传递给宏,例如算术表达式或任何不是单个变量的表达式。应该很容易看出,使用abs(1+2),扩展的-(1 + 2) 将给出与(-1 + 2) 不同的结果。这就是为什么-(x) 更正确的原因。

不幸的是,这两个宏都不安全,您应该使用内联函数来代替这样的事情。考虑:

abs (x++); // expands to ((x++) < 0 ? - (x++) : (x++))

这显然是宏的错误,但如果使用内联函数代替它会正常工作。

使用宏代替函数还有其他问题。见this question

编辑:注意问题是关于 C 的,内联函数可能仅在 C99 中可用。

【讨论】:

  • 也许另一种有趣的方式来说明这个问题是通过考虑将-x 作为参数传递意味着什么,因为扩展会减少x,哦人性! =)
  • @Mark:那是错误的。预处理器处理标记 (pp-tokens),因此当 -a 作为参数传递时,宏扩展中的 -x 会产生两个单独的标记,--a ,而不是--a。较旧的 C 处理器可以这样做,但标准禁止这样做。另请参阅 gcc 的 -traditional-cpp 选项的描述。 gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/…
【解决方案2】:

额外的括号可以解决各种相关问题。我会一一介绍:

试试:int y = abs( a ) + 2

假设您使用:

#define abs(x)  (x<0)?-x:x
...
    int y = abs( a ) + 2

这扩展为int y = (a&lt;0)?-a:a+2+2 仅绑定到错误结果。 2 仅在 a 为正时添加,而不是在它为负时添加。所以我们需要括号括起来:

#define abs(x)  ( (x<0) ? -x : x )

试试:int y = abs(a+b);

但是我们可能有int y = abs(a+b) 扩展为int y = ( (a+b&lt;0) ? -a+b : a+b)。如果 a + b 为负数,则当它们为结果相加时 b 不会被否定。所以我们需要把-xx放在括号里。

#define abs(x)  ( (x<0) ? -(x) : x )

试试:int y = abs(a=b);

这应该是合法的(虽然不好),但它会扩展为int y = ( (a=b&lt;0)?-(a=b):a=b );,它试图将最后的 b 分配给三元组。这不应该编译。 (请注意,它在 C++ 中执行。我必须使用 gcc 而不是 g++ 编译它才能看到它无法编译并出现“invalid lvalue in assignment”错误。)

#define abs(x)  ( (x<0) ? -(x) : (x) )

试试:int y = abs((a&lt;b)?a:b);

这扩展为int y = ( ((a&lt;b)?a:b&lt;0) ? -((a&lt;b)?a:b) : (a&lt;b)?a:b ),它将&lt;0 与b 组合在一起,而不是预期的整个三元组。

#define abs(x)  ( ( (x) < 0) ? -(x) : (x) )

最后,x 的每个实例都容易出现一些需要括号来解决的分组问题。

常见问题:运算符优先级

所有这些的共同点是operator precedence:如果您在abs(...) 调用中放置一个优先级低于宏中使用x 的运算符,那么它将错误地绑定。例如,abs(a=b) 将扩展为 a=b&lt;0,这与 a=(b&lt;0) 相同......这不是调用者的意思。

实施abs的“正确方法”

当然,无论如何,这是实现 abs 的错误方法...如果您不想使用内置函数(并且应该,因为它们将针对您移植到的任何硬件进行优化),那么它应该是内联模板(如果使用 C++),原因与 Meyers、Sutter 等人讨论重新实现 min 和 max 函数时提到的相同。 (其他回答也提到过:abs(x++)会怎样?)

在我的脑海中,一个合理的实现可能是:

template<typename T> inline const T abs(T const & x)
{
    return ( x<0 ) ? -x : x;
}

这里可以省略括号,因为我们知道 x 是单个值,而不是宏的任意扩展。

更好的是,正如 Chris Lutz 在下面的 cmets 中指出的那样,您可以使用模板专业化来调用优化版本(abs、fabs、labs)并获得类型安全、支持非内置类型以及性能。

测试代码

#if 0
gcc $0 -g -ansi -std=c99 -o exe && ./exe
exit
#endif




#include <stdio.h>

#define abs1(x)  (x<0)?-x:x
#define abs2(x)  ((x<0)?-x:x)
#define abs3(x)  ((x<0)?-(x):x)
#define abs4(x)  ((x<0)?-(x):(x))
#define abs5(x)  (((x)<0)?-(x):(x))


#define test(x)     printf("//%30s=%d\n", #x, x);
#define testt(t,x)  printf("//%15s%15s=%d\n", t, #x, x);

int main()
{
    test(abs1( 1)+2)
    test(abs1(-1)+2)
    //                    abs1( 1)+2=3
    //                    abs1(-1)+2=1

    test(abs2( 1+2))
    test(abs2(-1-2))
    //                    abs2( 1+2)=3
    //                    abs2(-1-2)=-1

    int a,b;
    //b =  1; testt("b= 1; ", abs3(a=b))
    //b = -1; testt("b=-1; ", abs3(a=b))
    // When compiled with -ansi -std=c99 options, this gives the errors:
    //./so1a.c: In function 'main':
    //./so1a.c:34: error: invalid lvalue in assignment
    //./so1a.c:35: error: invalid lvalue in assignment

    // Abs of the smaller of a and b. Should be one or two.
    a=1; b=2; testt("a=1; b=2; ", abs4((a<b)?a:b))
    a=2; b=1; testt("a=2; b=1; ", abs4((a<b)?a:b))
    //               abs4((a<b)?a:b)=-1
    //               abs4((a<b)?a:b)=1


    test(abs5( 1)+2)
    test(abs5(-1)+2)
    test(abs5( 1+2))
    test(abs5(-1-2))
    b =  1; testt("b= 1; ", abs5(a=b))
    b = -1; testt("b=-1; ", abs5(a=b))
    a=1; b=2; testt("a=1; b=2; ", abs5((a<b)?a:b))
    a=2; b=1; testt("a=2; b=1; ", abs5((a<b)?a:b))
}

输出

                    abs1( 1)+2=3
                    abs1(-1)+2=1
                    abs2( 1+2)=3
                    abs2(-1-2)=-1
     a=1; b=2; abs4((a<b)?a:b)=-1
     a=2; b=1; abs4((a<b)?a:b)=1
                    abs5( 1)+2=3
                    abs5(-1)+2=3
                    abs5( 1+2)=3
                    abs5(-1-2)=3
         b= 1;       abs5(a=b)=1
         b=-1;       abs5(a=b)=1
     a=1; b=2; abs5((a<b)?a:b)=1
     a=2; b=1; abs5((a<b)?a:b)=1

【讨论】:

  • 请注意,如果您愿意,可以重载模板化absintlong(和long long)版本以使用C 标准abs() 和@987654357 @ 函数(如果你的 C++ 编译器有一些更简单的 C99 函数,还有 llabs())。
  • 好主意,有机会我会提到。这样用户就不用担心调用哪个函数了,我们免费得到正确的函数,没有开销。
  • 我喜欢你的回答,尽管它对 C++ 有点沉重,尽管问题被标记为“C”。
猜你喜欢
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
  • 2011-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多