C宏问题 - (x)vs(-x)

Cry*_*tal 22 c macros

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

像绝对值的宏这样的函数的正确实现是:

#define abs(x) ((x)<0 ? (-x) : (x))
#define abs(x) ((x)<0 ? -(x) : (x))
Run Code Online (Sandbox Code Playgroud)

为什么第二个与第一个相比是正确的?

为什么你必须使用所有().喜欢涉及的规则是什么?每个变量都需要一个()?谢谢.

mar*_*ets 30

额外括号解决了各种相关问题.我将逐一介绍它们:

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

我们假设您使用:

#define abs(x)  (x<0)?-x:x
...
    int y = abs( a ) + 2
Run Code Online (Sandbox Code Playgroud)

这扩展到int y = (a<0)?-a:a+2.在+2仅绑定到错误的结果.2仅在a为正时添加,而在为负时添加.所以我们需要围绕整个事项的括号:

#define abs(x)  ( (x<0) ? -x : x )
Run Code Online (Sandbox Code Playgroud)

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

但那时我们可能int y = abs(a+b)会扩展到哪个int y = ( (a+b<0) ? -a+b : a+b).如果a + b为负,那么当他们为结果添加时,b不会被否定.因此,我们需要把x-x在括号中.

#define abs(x)  ( (x<0) ? -(x) : x )
Run Code Online (Sandbox Code Playgroud)

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

这应该是合法的(尽管很糟糕),但它扩展到int y = ( (a=b<0)?-(a=b):a=b );试图将最终的b分配给三元组.这不应该编译.(注意它在C++中.我必须使用gcc而不是g ++编译它,看它无法使用"赋值中的无效左值"错误进行编译.)

#define abs(x)  ( (x<0) ? -(x) : (x) )
Run Code Online (Sandbox Code Playgroud)

尝试: int y = abs((a<b)?a:b);

这扩展为int y = ( ((a<b)?a:b<0) ? -((a<b)?a:b) : (a<b)?a:b ),<0将b与b组合,而不是按预期组合整个三元组.

#define abs(x)  ( ( (x) < 0) ? -(x) : (x) )
Run Code Online (Sandbox Code Playgroud)

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

常见问题:运算符优先级

所有这些中的共同线程是运算符优先级:如果您在abs(...)调用中放置一个优先级较低的操作符x,那么宏中使用的位置就会出现问题,那么它将绑定不正确.例如,abs(a=b)将扩展到a=b<0a=(b<0)... 相同的......不是调用者的意思.

实施的"正确方式" abs

当然,这是实现abs的错误方法...如果你不想使用内置函数(你应该,因为它们将针对你移植到的任何硬件进行优化),那么它应该是一个内联模板(如果使用C++)的原因与Meyers,Sutter等人讨论重新实现最小和最大函数时提到的相同.(其他答案也提到过:会发生什么abs(x++)?)

在我的头脑中,合理的实施可能是:

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

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

更好的是,正如Chris Lutz在下面的评论中指出的那样,您可以使用模板专业化来调用优化版本(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))
}
Run Code Online (Sandbox Code Playgroud)

产量

                    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
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢你的答案,尽管它在C++上有点沉重,即使这个问题被标记为'C'. (2认同)

Dan*_*son 14

是的,每个变量都需要直接围绕它.

原因是因为你可以将事物传递给不是"好"的宏,比如算术表达式或任何不是单个变量的表达式.应该很容易看出,abs(1+2)扩展-(1 + 2)将产生不同的结果(-1 + 2).这就是为什么-(x)更正确的原因.

不幸的是,宏都不安全,你应该使用内联函数来代替这样的东西.考虑:

abs (x++); // expands to ((x++) < 0 ? - (x++) : (x++))
Run Code Online (Sandbox Code Playgroud)

这显然是宏的错误,但如果使用内联函数,它将正常工作.

使用宏而不是函数也存在其他问题.看到这个问题.

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

  • @Mark:那是错的.预处理器在令牌(*pp-tokens*)上工作,所以当`-a`作为参数传递时,宏扩展中的`-x`会产生两个独立的标记,`-`和`-a`,而不是`--a`.较旧的C处理器做到了这一点,但标准禁止它.另请参阅gcc的`-traditional-cpp`选项的说明.http://gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/Preprocessor-Options.html#Preprocessor-Options (6认同)