Макросы и постинкремент - PullRequest
       16

Макросы и постинкремент

4 голосов
/ 15 сентября 2011

Вот еще несколько странных макросов, на которые я надеялся пролить свет:

#define MAX(a,b) (a>b?a:b)

void main(void)
{
  int a = 3, b=4;

  printf("%d %d %d\n",a,b,MAX(a++,b++));
}

Выход 4 6 5. Значение b увеличивается вдвое, но не раньше, чем MAX отобразит его значение. Кто-нибудь может сказать мне, почему это происходит, и как можно предсказать такое поведение? (Еще один пример того, почему следует избегать макросов!)

Ответы [ 8 ]

6 голосов
/ 15 сентября 2011

Макросы делают подстановку текста. Ваш код эквивалентен:

printf("%d %d %d\n",a,b, a++ > b++ ? a++ : b++);

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

Но, как и в случае с любым другим UB, если вы посмотрите на него некоторое время, вы сможете найти объяснение того, что ваша реализация на самом деле сделала, чтобы получить результат, который вы видите. Порядок оценки аргументов не определен, но мне кажется, что аргументы были оценены в порядке справа налево. Итак, во-первых, a и b увеличиваются один раз. a не больше b, поэтому b снова увеличивается, и результат условного выражения равен 5 (то есть b после первого приращения и перед вторым).

Такое поведение ненадежно - другая реализация или та же реализация в другой день может дать разные результаты из-за оценки аргументов в другом порядке или теоретически может даже аварийно завершить работу из-за проблемы с точкой последовательности.

2 голосов
/ 15 сентября 2011

Когда препроцессор читает строку, он заменяет MAX (a ++, b ++) в printf на (a ++> b ++? A ++; b ++)

Таким образом, ваша функция становится

    printf(a,b,(a++>b++?a++;b++));

Здесь порядок вычисления «зависит от компилятора».

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

В каждой точке последовательности - побочные эффекты всех предыдущих выражений.будет завершено (все переменные вычисления будут завершены).Вот почему вы не можете полагаться на выражения, такие как:

    a[i] = i++;

, поскольку для операторов присваивания, приращения или индекса не указана точка последовательности, вы не знаете, когда произойдет влияние приращения на i,«Между предыдущей и следующей точкой последовательности объект должен иметь свое сохраненное значение, измененное не более одного раза путем оценки выражения.Кроме того, предыдущее значение должно быть прочитано только для определения значения, которое будет сохранено ».Если программа нарушает эти правила, результаты в любой конкретной реализации являются совершенно непредсказуемыми (неопределенными).

- Точки последовательности, изложенные в Стандарте, следующие:

1) Точкавызова функции после вычисления ее аргументов.

2) Конец первого операнда оператора &&.

3) Конец первого операнда ||operator.

4) Конец первого операнда условного оператора?:

5) Конец каждого операнда оператора запятой.

6)Завершение оценки полного выражения.Они следующие:

Оценка инициализатора автообъекта.

Выражение в «обычном» выражении - выражение, за которым следует точка с запятой.

Управляющие выражения всделать, в то время как, если, переключить или для операторов.

Два других выражения в операторе для.

Выражение в операторе возврата.

2 голосов
/ 15 сентября 2011

В макросе параметры просто заменяются аргументами;поэтому аргументы могут оцениваться несколько раз, если они присутствуют несколько раз в макросе.

Ваш пример:

MAX(a++,b++)

Расширяется до этого:

a++>b++?a++:b++

Я думаювам не нужно больше объяснений:)

Вы можете предотвратить это, присвоив каждому параметру временную переменную:

#define MAX(a,b) ({   \
    typeof(a) _a = a; \
    typeof(b) _b = b; \
    a > b ? a : b;    \
})

(хотя этот использует несколько расширений GCC)

Или используйте встроенные функции:

int MAX(int a, int b) {
    return a > b ? a : b;
}

Это будет так же хорошо, как макрос во время выполнения.

Или не делайте приращений в аргументах макроса:

a++;
b++;
MAX(a, b)
1 голос
/ 15 сентября 2011

Таким образом, ваше расширение дает (с поправкой на ясность):

(a++ > b++) ? a++ : b++

... поэтому сначала вычисляется (a++ > b++), давая по одному приращению каждый и выбирая ветвь на основе еще не увеличенных значенийa и b.Выбрано выражение 'else', b++, которое делает второе приращение на b, которое уже было увеличено в тестовом выражении.Поскольку это постинкремент, значение b перед вторым инкрементом равно printf().

1 голос
/ 15 сентября 2011

Если я прав, это происходит:

с заменой MAX на (a> b ...) у вас есть printf ("% d% d% d \ n", a, b, (a ++> b ++? A ++: b ++));

Сначала проверяется a ++> b ++, а затем оба значения увеличиваются (a = 4, b = 5). Затем активируется второй b ++, но, поскольку он является постинкрементным, он увеличивается после вывода второго значения b = 5.

Извините за мой плохой английский, но я надеюсь, вы понимаете это ?! : D

Привет из Германии; -)

Ralf

1 голос
/ 15 сентября 2011

Макросы оцениваются препроцессором, который тупо заменяет все согласно определениям макросов. В вашем случае MAX(a++, b++) становится (a++>b++) ? a++ : b++.

0 голосов
/ 15 сентября 2011


Есть две причины, по которым вы получаете результат:

  1. Макрос - это не что иное, как код, который раскрывается и вставляется, когдавы компилируетеИтак, ваш макрос

    MAX(a,b) (a>b?a:b)
    

    становится этим

    a++>b++?a++:b++
    

    , а ваши результирующие функции таковы:

    printf("%d %d %d\n",a,b, a++>b++?a++:b++);
    

    Теперь должно быть понятно, почему b увеличивается дважды:первый в сравнении, второй при возврате.Это не неопределенное поведение, оно четко определено, просто проанализируйте код, и вы увидите, чего именно ожидать.(это в некотором смысле предсказуемо)

  2. Вторая проблема здесь заключается в том, что в C параметры передаются из стека от первого к первому, поэтому все приращения будут выполняться до того, как вы напечатаетеисходные значения a и b, даже если они указаны первыми.Попробуйте эту строку кода, и вы поймете, что я имею в виду:

    int main(void)
    {
        int a = 3, b=4;
        printf("%d %d %d\n",a,b, b++);
        return 0;
    }
    

Вывод будет 3 5 4. Я надеюсь, что это объясняет поведение и поможет вампредсказать результат.
НО , последний пункт зависит от вашего компилятора

0 голосов
/ 15 сентября 2011

Я думаю, что спрашивающий ожидал начала вывода:

3 4 ...

вместо:

4 6 ...

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

Я думаю (и кто-то публикует комментарий, если он неправильный), что это определено в стандарте C (и C ++).

Обновление

Порядок оценки определен, но он определен как неопределенный (спасибо Стиву). Ваш компилятор просто так делает. Я думаю, что я запутался между порядком оценки и порядком передачи параметров.

...