Необходимость в скобках в макросах в C [дубликат]


На этот вопрос уже есть ответ здесь:

Я попытался поиграть с определением макроса SQR в следующем коде:

#define SQR(x) (x*x)
int main()
{
    int a, b=3;
    a = SQR(b+5);      // Ideally should be replaced with (3+5*5+3), though not sure.
    printf("%dn",a);
    return 0;
}

Он печатает 23. Если я изменю определение макроса на SQR(x) ((x)*(x)), то результат будет таким, как и ожидалось, 64. Я знаю, что вызов макроса в C заменяет вызов определением макроса, но я до сих пор не могу понять, как он вычисляется 23.

8 25

8 ответов:

Макросы препроцессора выполняют замену текста перед компиляцией кода таким образом, что SQR(b+5) переводится в (b+5*b+5) = (6b+5) = 6*3+5 = 23

Регулярные вызовы функций вычисляют значение параметра (b+3) перед передачей его функции, но поскольку макрос является предварительно скомпилированной заменой, алгебраический порядок операций становится очень важным.

Потому что (3+5*3+5 == 23).

Тогда как ((3+5)*(3+5)) == 64.

Лучший способ сделать это - не использовать макрос :

inline int SQR(int x) { return x*x; }

Или просто напишите x*x.

Рассмотрим замену макроса с помощью этого макроса:

#define SQR(x) (x*x)

, используя b+5 в качестве аргумента. Сделайте замену сами. В вашем коде SQR(b+5) станет: (b+5*b+5), или (3+5*3+5). Теперь запомните вашиправила приоритета операторов : * Перед +. Таким образом, это оценивается как: (3+15+5), или 23.

Вторая версия макроса:

#define SQR(x) ((x) * (x))

Правильно, потому что вы используете parens, чтобы отделить ваши аргументы макроса от эффектов оператора старшинство.

Эта страница объясняя предпочтение оператор C имеет хороший график. вот соответствующий раздел справочного документа C11.

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

Макрос расширяется до

 a = b+5*b+5;

То есть

 a = b + (5*b) + 5;

Итак, 23.

Макрос - это просто прямая текстовая подстановка. После предварительной обработки код выглядит следующим образом:

int main()
{
    int a, b=3;
    a = b+5*b+5;
    printf("%d\n",a);
    return 0;
}
Умножение имеет более высокий приоритет оператора, чем сложение, поэтому оно выполняется перед двумя сложениями при вычислении значения для a. Добавление круглых скобок в определение макроса устраняет проблему, делая его:
int main()
{
    int a, b=3;
    a = (b+5)*(b+5);
    printf("%d\n",a);
    return 0;
}

Операции, заключенные в скобки, вычисляются перед умножением, поэтому сложение происходит сначала, и вы получаете результат a = 64, который вы ожидать.

После предварительной обработки, SQR(b+5) будет расширен до (b+5*b+5). Очевидно, что это неверно.

Есть две распространенные ошибки в определении SQR:

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

    #define SQR(x) ((x)*(x))
    
  2. Оценивайте аргументы макроса более одного раза, так что если те аргументы-это выражения, которые имеют побочный эффект, этот побочный эффект может быть использован более одного раза. Например, рассмотрим результат SQR(++x).

    Используя расширение GCC typeof , эту проблему можно решить следующим образом

    #define SQR(x) ({ typeof (x) _x = (x); _x * _x; })
    

Обе эти проблемы можно решить, заменив этот макрос встроенной функцией

   inline int SQR(x) { return x * x; }

Для этого требуется встроенное расширение GCC или C99, см. 6.40 встроенная функция так же быстра, как макрос.

Потому что макросы - это просто замена строки,и это происходит до завершения процесса. Компилятор не сможет увидеть макропеременную и ее значение. Например: если макрос определен как

#define BAD_SQUARE(x)  x * x 

И называется так

BAD_SQUARE(2+1) 

Компилятор увидит это

2 + 1 * 2 + 1

, что приведет, возможно, к неожиданному результату

5

Чтобы исправить это поведение, вы всегда должны окружать макропеременные скобками, такими как как

#define GOOD_SQUARE(x)  (x) * (x) 

Когда этот макрос вызывается ,например, так

GOOD_SQUARE(2+1)

Компилятор увидит это

(2 + 1) * (2 + 1)

Что приведет к

9

Кроме того, вот полный пример, чтобы еще больше проиллюстрировать точку

#include <stdio.h>

#define BAD_SQUARE(x)  x * x 
// In macros alsways srround the variables with parenthesis
#define GOOD_SQUARE(x)  (x) * (x) 

int main(int argc, char const *argv[])
{
    printf("BAD_SQUARE(2) = : %d \n", BAD_SQUARE(2) ); 
    printf("GOOD_SQUARE(2) = : %d \n", GOOD_SQUARE(2) ); 
    printf("BAD_SQUARE(2+1) = : %d ; because the macro will be \
subsituted as 2 + 1 * 2 + 1 \n", BAD_SQUARE(2+1) ); 
    printf("GOOD_SQUARE(2+1) = : %d ; because the macro will be \
subsituted as (2 + 1) * (2 + 1) \n", GOOD_SQUARE(2+1) ); 

    return 0;
}

Просто заключите каждый аргумент в макрорасширении в круглые скобки.

#define SQR(x) ((x)*(x))

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