Вопрос макроса C - (x) против (-x) - PullRequest
21 голосов
/ 08 января 2010

Я прохожу ответы на вопросы моего профессора на вопрос:

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

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

Почему второй вернее первого?

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

Ответы [ 2 ]

28 голосов
/ 08 января 2010

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

Попробуйте: 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 добавляется только тогда, когда а положительно, а не когда оно отрицательно. Поэтому нам нужно заключить в скобки все это:

#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 троичной. Это не должно компилироваться. (Обратите внимание, что это происходит в C ++. Мне пришлось скомпилировать его с помощью 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 в любом случае ... если вы не хотите использовать встроенные функции (и вам следует, потому что они будут оптимизированы для любого оборудования, на которое вы портируете), тогда это должен быть встроенным шаблоном (если используется C ++) по тем же причинам, которые были упомянуты, когда Мейерс Саттер и др. обсуждали повторную реализацию функций 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
14 голосов
/ 08 января 2010

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...