在#define语句中使用if语句是否可行?

28

我正在尝试制作一个带有以下公式的宏:(a^2/(a+b))*b,并且我想确保不会出现除以零的情况。

#define SUM_A( x, y ) if( x == 0 || y == 0) { 0 } else { ( ( ( x * x ) / ( ( x ) + ( y ) ) ) * ( y ) )}

然后我在主函数中调用宏:

float a = 40, b = 10, result; 
result = SUM_A(a, b); 
printf("%f", result);

我尝试在if函数周围加上括号,但是在if语句之前一直出现语法错误。我也尝试过使用return,但我读到某个地方说你不应该在define中使用它。

8个回答

36

你不能使用if语句,因为#define是由预处理器解释的,输出结果将会是:

 result=if( x == 0 || y == 0) { 0 } else { ( ( ( x * x ) / ( ( x ) + ( y ) ) ) * ( y ) )}

这是错误的语法。

但是另一种选择是使用三元运算符。将您的定义更改为

#define SUM_A( x, y )  ((x) == 0 || (y) == 0 ? 0 : ( ( ( (x) * (x) ) / ( ( x ) + ( y ) ) ) * ( y ) ))

请记得始终将定义放在括号中,以避免在替换时出现语法错误。


#define中可以使用任何东西,只需要注意预处理器即可。请参见下面@Saeed的答案。 - Yuval

21
据我所知,你试图做的事情(使用if语句并从宏中返回值)在ISO C中是不可能的...但是在语句表达式(GNU扩展)中有一定的可能性。
由于#define实质上只是复杂的文本查找和替换,你必须非常小心地处理它们的扩展。我发现这在gcc和clang上默认工作:
#define SUM_A(x, y)                                     \
({                                                      \
    float answer;                                       \
    if ((x) == 0 || (y) == 0) {                         \
        answer = 0;                                     \
    } else {                                            \
        answer = ((float)((x)*(x)) / ((x)+(y))) * (y);  \
    }                                                   \
    answer;                                             \
})
// Typecasting to float necessary, since int/int == int in C

这个宏的简要解释如下:

  • 每行末尾的\表示续行(即告诉编译器“这个宏在下一行继续”)
  • ({是语句表达式(GNU扩展;不是标准C的一部分)
  • 虽然没有必要,但为了避免操作符优先级问题,最好在每个参数的使用上都加上括号。例如,如果x2+1,那么(x)*(x)会展开为(2+1)*(2+1),结果为9(我们想要的),但x*x则会展开为2+1*2+1,结果为5(不是我们想要的)
  • 在语句表达式中,最后一行的作用类似于返回值(因此在末尾有answer;

这样应该可以得到您想要的结果,而且没有理由不能扩展以包括多个else if(尽管正如其他答案所指出的那样,如果可以的话最好使用三元运算符)。


7

if引入的是一个语句而不是表达式。请使用"三目"(条件)运算符:

#define SUM_A(x, y) (((x) == 0 || (y) == 0)? 0: ((((x) * (x)) / ((x) + (y))) * (y)))

或者将此函数变为内联函数:

inline float sum_a(float x, float y)
{
    if (x == 0 || y == 0)
        return 0;
    else
        return ((x * x) / (x + y)) * y;
}

这样做可以避免多次评估 x 和/或 y 的问题,并且更易于阅读,但它确实固定了 xy 的类型。您还可以删除 inline 并让编译器决定是否值得内联此函数(inline 不能保证它会执行内联)。

1
这个函数的问题在于它强制你使用 float 作为数据类型。也许这没关系,但也许不是这样。 - SirGuy

3

你的宏存在多个问题:

  • 它会扩展为一个语句,因此不能用作表达式。

  • 在扩展时括号没有被正确地加上:如果使用任何不是变量名或常量的内容调用此宏将会产生问题。

  • 参数会被多次评估:如果使用具有副作用的参数(例如SUM_A(a(), b())SUM_A(*p++, 2))调用宏,副作用将会发生多次。

为避免这些问题,可以使用一个函数来解决,可能定义为static inline 以帮助编译器进行更多优化(这是可选项,现代编译器会自动执行):

static inline int SUM_A(float x, float y) {
    if (x == 0 || y == 0)
        return 0; 
    else
        return x * x / (x + y) * y;
}

注意事项:

  • 此函数使用浮点数算术运算,而宏根据其参数的实际类型不一定使用浮点数。
  • 该测试不能防止除以零:SUM_A(-1, 1)仍会执行。
  • 除以零不一定是问题:对于浮点参数,它将产生一个无穷大或NaN,而不是运行时错误。

1

是的,你可以在宏中使用if语句。你需要正确地格式化它。以下是一个示例:

#define MY_FUNCTION( x )  if( x ) { PRINT("TRUE"); } else { PRINT("FALSE"); } 

4
该解决方案不适合,因为带有尾随 ; 的宏调用会扩展为2个语句。使用 do / while(0) 包装来避免这种情况。 - chqrlie
你正在回答他从未问过的问题。他要求一个返回值的宏。 - Sazzad Hissain Khan

1
问题在于,if语句不是表达式,不返回值。此外,在这种情况下使用宏没有什么好的理由。事实上,它可能会导致非常严重的性能问题(取决于您传递的宏参数)。您应该使用函数代替。

0

我经常使用带有条件的宏,它们确实有合法的用途。

我有一些本质上是块状结构的结构体,其中所有内容都是 uint8_t 流。

为了使内部结构更易读,我使用条件宏。

例如...

#define MAX_NODES 10
#define _CVAL16(x)(((x) <= 127) ? (x) : ((((x) & 127) | 0x80) ), ((x) >> 7))  // 1 or 2 bytes emitted <= 127 = 1 otherwise 2

现在要在数组内部使用宏...

uint8_t arr_cvals[] = { _CVAL16(MAX_NODES), _CVAL16(345) };

数组中发出三个字节,第一个宏发出1,第二个宏发出2个字节。这在编译时进行评估,只是使代码更易读。

我还有...例如...

#define _VAL16(x) ((x) & 255), (((x) >> 8) & 255)

对于原问题......也许该人想要将结果与常量一起使用,但再次强调的是它将在何处以及如何使用。

#define SUM_A(x, y) (!(x) || !(y)) ? 0 : ((x) * (x) / ((x) + (y)) * (y))
float arr_f[] = { SUM_A(0.5f, 0.55f), SUM_A(0.0f, -1.0f), SUM_A(1.0f, 0.0f) };

在运行时可以有...

float x;
float y;

float res = SUM_A(x,y); // note ; on the end

我有一个程序,可以创建字体,并将其作为代码包含在C程序中。大多数值都被封装在宏中,将32位值分成4个字节、浮点数分成4个字节等。


0

您可以将条件语句转换为简单表达式。条件评估结果为01

// pseudo-code
// if (<something>) { 0; } else { 42; }
// if (!<something>) { 42; } else { 0; }
// !<something> * 42;

在你的具体情况下
// if ((x == 0) || (y == 0)) { 0; } else { (x)(y)expression; }
// if ((x != 0) && (y != 0)) { (x)(y)expression; }
// ((x != 0) && (y != 0)) * ( (x)(y)expression );

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