Подтвердить что ты не робот

C Макро Вопрос - (x) vs (-x)

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

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

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

Почему второй правильный и первый?

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

4b9b3361

Ответ 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

Ответ 2

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

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

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

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

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

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

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