Следующий код дает разные выходные данные для разных компиляторов;
#include <stdio.h>
void fun(int x, int y, int z) /* function definition */
{
printf("%d %d %d \n", x, y, z); /* 6 6 6 */
}
int main()
{
int a = 3;
fun(++a, ++a, ++a); /* function call */
return 0;
}
На данный момент код дал результаты
6 6 66 5 44 5 6на разных компиляторах.
Имеет ли такое поведение какое-либо отношение к порядку оценки аргументов функции или это может быть связано с соглашением о вызовах (cdecl)? Изменит ли переход на соглашение (если это возможно) каким-либо образом вывод вышеупомянутого кода?
Фрагмент кода взят из книги, в которой пытались представить соглашения о вызовах в C. Я новичок в C, поэтому, пожалуйста, сделайте его как можно проще или предоставьте контекст.
Я не думаю, что соглашения о вызовах имеют какое-либо влияние на порядок оценки аргументов. Они только говорят: как только у вас есть значения, которые вы хотите передать, как их передать и как получить возвращаемое значение.
Упоминается ли в книге где-то «неопределённое поведение», связанное с этим кодом: fun(++a, ++a, ++a)? Если нет, то тебе, наверное, стоит выбросить книгу.
OTOH это ясно показывает, что порядок оценки аргументов функции в gcc и clang различен. Но, как упоминалось в других комментариях, это не связано с соглашением о вызовах.
Не пытайтесь повторить это на DeathStation 9000 :-)
@Quorthon, учтите, что 3 ++a могут произойти одновременно, что приведет к конфликту шины и сбою программы.





Соглашение о вызовах определяет, каким должно быть состояние компьютера в момент вызова функции и в момент ее возврата. Он ничего не говорит о том, как компьютер пришел в это состояние, и, следовательно, ничего не говорит о порядке, в котором должны оцениваться аргументы функции.
Например, соглашение о вызовах может гласить, что для определенного функционального интерфейса аргументы 1, 2, 3 и 4 должны находиться соответственно в регистре процессора A, регистре процессора B, ячейке X в стеке и ячейке Y в стеке. куча. Однако вызывающая программа может вычислить аргумент 2 и поместить его в регистр B, затем вычислить аргумент 3 и поместить его в ячейку X, затем вычислить аргумент 4 и поместить его в ячейку Y, а затем вычислить аргумент 1 и поместить его в регистр A. Пока все аргументы помещены в необходимые места к моменту вызова функции, соглашение о вызовах выполняется.
В fun(++a, ++a, ++a) ни соглашение о вызовах, ни стандарт C не определяют порядок, в котором оцениваются аргументы. Компилятор может генерировать инструкции для оценки аргументов в любом порядке. Кроме того, оператор предварительного приращения состоит из двух операций: увеличения значения операнда и использования значения операнда. Эти операции не связаны друг с другом, поэтому три приращения и три использования значений a в fun(++a, ++a, ++a) можно переставить в любом порядке.
Стандарт C гласит, что когда множественные модификации скалярного объекта не упорядочены или модификация скалярного объекта не упорядочена с использованием его значения, поведение программы не определено. Итак, поведение программы, выполняющей fun(++a, ++a, ++a), не определяется стандартом C.
Соглашения о вызовах могут влиять на выбор, сделанный компилятором или авторами компилятора, но нет требования, чтобы порядок вычислений был каким-либо образом связан с соглашениями о вызовах.