C макро-вопрос - (x) по сравнению с (-x)

Я прохожу ответы теста от своего преподавателя, и вопрос был:

корректная реализация функции как макрос для абсолютного значения:

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

Почему второй корректен по сравнению с первым?

И почему необходимо использовать весь (). Как какой включены правила? Каждой переменной нужно ()? Спасибо.

22
задан Dan Olson 8 January 2010 в 04:02
поделиться

2 ответа

Существуют различные смежные проблемы, которые решаются с помощью лишних скобок. Я просмотрю их один за другим:

Попробуйте: 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++, чтобы он не скомпилировался с ошибкой "invalid lvalue in assignment")

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

Попробуйте: int y = abs((a

Это расширяется до int y = ( ((a, который группирует <0 с b, а не весь троичник, как предполагалось.

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

В конце концов, каждый экземпляр x склонен к некоторой проблеме группировки, для решения которой необходимы скобки.

Общая проблема: приоритет оператора

Общим потоком во всех этих случаях является приоритет оператора : если в вашем вызове abs(...) поставить оператор, имеющий меньший приоритет, чем что-либо вокруг, где в макросе используется x, то это приведет к некорректной привязке. Например, abs(a=b) расширится до a=b<0, что равнозначно a=(b<0)... это не то, что имел в виду вызывающий абонент.

The "Right Way" to Implement abs

Конечно, это неправильный способ реализации abs в любом случае... если вы не хотите использовать встроенные функции (а вы должны, потому что они будут оптимизированы для любого аппаратного обеспечения, на которое вы переносите), то это должен быть встроенный шаблон (при использовании C++) по тем же причинам, которые упоминались, когда Мейерс, Sutter, и другие обсуждают переинсталляцию функций min и max. (В других ответах также упоминалось: что происходит с abs(x++)?)

С верхушки головы, разумной реализацией может быть:

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

Здесь можно не заключать в скобки, так как мы знаем, что x - это единственное значение, а не какое-то произвольное расширение из макроса.

Еще лучше, как отметил Крис Лутц в комментариях ниже, можно использовать специализацию шаблонов для вызова оптимизированных версий (abs, fabs, labs) и получить все преимущества безопасности типов, поддержки неbuiltin-типов и производительности.

Test Code

#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))
}

Output

                    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
30
ответ дан 29 November 2019 в 04:33
поделиться

Да, каждая переменная должна быть заключена в круглые скобки.

Причина в том, что вы можете передавать в макрос вещи, которые не являются «хорошими», например арифметические выражения или любое выражение, которое не является отдельной переменной. Должно быть легко увидеть, что с abs (1 + 2) расширенный - (1 + 2) даст другой результат, чем (- 1 + 2) . Вот почему - (x) более правильно.

К сожалению, ни один из макросов не является безопасным, вместо этого вы должны использовать встроенную функцию для подобных вещей. Учтите:

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

Это явно не так с макросом, но он работал бы правильно, если бы вместо него использовалась встроенная функция.

Есть и другие проблемы с использованием макросов вместо функций. См. этот вопрос .

Изменить: учитывая, что вопрос касается C, встроенные функции могут быть доступны только в C99.

14
ответ дан 29 November 2019 в 04:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: