Ответ 1
Существуют различные связанные проблемы, которые разрешают дополнительные круглые скобки. Я пройду через них один за другим:
Попробуйте: int y = abs( a ) + 2
Предположим, вы используете:
#define abs(x) (x<0)?-x:x
...
int y = abs( a ) + 2
Это расширяется до int y = (a<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<0) ? -a+b : a+b)
. Если a + b отрицательно, то b не отрицается, когда они добавляют для результата. Поэтому нам нужно положить x
из -x
в круглые скобки.
#define abs(x) ( (x<0) ? -(x) : x )
Попробуйте: int y = abs(a=b);
Это должно быть законным (хотя и плохим), но оно расширяется до int y = ( (a=b<0)?-(a=b):a=b );
, которое пытается назначить последний b тройному. Это не должно компилироваться. (Обратите внимание, что это происходит на С++. Мне пришлось скомпилировать его с помощью gcc вместо g++, чтобы он не смог скомпилировать с ошибкой "недопустимый lvalue в присваивании".)
#define abs(x) ( (x<0) ? -(x) : (x) )
Попробуйте: int y = abs((a<b)?a:b);
Это расширяется до int y = ( ((a<b)?a:b<0) ? -((a<b)?a:b) : (a<b)?a:b )
, который группирует <0
с b, а не весь тройной, как предполагалось.
#define abs(x) ( ( (x) < 0) ? -(x) : (x) )
В конце концов, каждый экземпляр x
подвержен некоторой проблеме группировки, требующей скобки.
Общая проблема: приоритет оператора
Общий поток во всех этих случаях приоритет оператора: если вы помещаете оператор в ваш вызов abs(...)
, который имеет более низкий приоритет, тогда что-то где x
используется в макросе, тогда он будет связываться неправильно. Например, abs(a=b)
будет расширяться до a=b<0
, что совпадает с a=(b<0)
... это не то, что имел в виду вызывающий объект.
"Правильный путь" для реализации abs
Конечно, это неправильный способ реализовать abs anyways... если вы не хотите использовать встроенные функции (и вы должны, потому что они будут оптимизированы для любого оборудования, к которому вы подключаетесь), тогда это должен быть встроенным шаблоном (если используется С++) по тем же причинам, о которых упоминалось, когда Meyers, Sutter, и др. обсуждают повторное выполнение функций min и max, (Другие ответы также упомянули об этом: что происходит с abs(x++)
?)
Сверху моей головы разумная реализация может быть:
template<typename T> inline const T abs(T const & x)
{
return ( x<0 ) ? -x : x;
}
Здесь можно оставить круглые скобки, так как мы знаем, что x - это одно значение, а не какое-либо произвольное расширение из макроса.
Еще лучше, как отметил Крис Лутц в комментариях ниже, вы можете использовать специализированную специализацию для вызова оптимизированных версий (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