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

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

#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 отобразит его значение. Может кто-нибудь, пожалуйста, скажите мне, почему это происходит и как можно предсказать такое поведение? (Другой пример, почему следует избегать макросов!)

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

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

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

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

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

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

Ваш пример:

 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) 

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

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

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

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

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

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

  a[i] = i++; 

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

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

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

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

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

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

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

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

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

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

Управляющие выражения в do, while, if, switch или для операторов.

Остальные два выражения в инструкции for.

Выражение в выражении return.

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

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

с заменой 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

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

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

… поэтому сначала оценивается (a++ > b++) , каждый раз указывая один шаг и выбирая ветвь на основе еще не увеличенных значений a и b . Выбирается выражение ‘else’, b++ , которое выполняет второй приращение на b , который уже был увеличен в тестовом выражении. Так как это post-increment, значение b перед вторым приращением присваивается printf() .

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

  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. Я надеюсь, что это объяснит поведение и поможет вам предсказать результат.
НО , последняя точка зависит от вашего компилятора

Я думаю, что вопросник ожидал, что выход начнется:

 3 4 ... 

вместо:

 4 6 ... 

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

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

Обновить

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