Разница между соглашением о вызовах и порядком оценки аргументов функции в C?

Следующий код дает разные выходные данные для разных компиляторов;

#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 6
  • 6 5 4
  • 4 5 6

на разных компиляторах.

Имеет ли такое поведение какое-либо отношение к порядку оценки аргументов функции или это может быть связано с соглашением о вызовах (cdecl)? Изменит ли переход на соглашение (если это возможно) каким-либо образом вывод вышеупомянутого кода?

Фрагмент кода взят из книги, в которой пытались представить соглашения о вызовах в C. Я новичок в C, поэтому, пожалуйста, сделайте его как можно проще или предоставьте контекст.

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

user2357112 06.06.2024 13:21

Я не думаю, что соглашения о вызовах имеют какое-либо влияние на порядок оценки аргументов. Они только говорят: как только у вас есть значения, которые вы хотите передать, как их передать и как получить возвращаемое значение.

Verpous 06.06.2024 13:21

Упоминается ли в книге где-то «неопределённое поведение», связанное с этим кодом: fun(++a, ++a, ++a)? Если нет, то тебе, наверное, стоит выбросить книгу.

Jabberwocky 06.06.2024 13:29

OTOH это ясно показывает, что порядок оценки аргументов функции в gcc и clang различен. Но, как упоминалось в других комментариях, это не связано с соглашением о вызовах.

Jabberwocky 06.06.2024 13:35

Не пытайтесь повторить это на DeathStation 9000 :-)

pmg 06.06.2024 13:54

@Quorthon, учтите, что 3 ++a могут произойти одновременно, что приведет к конфликту шины и сбою программы.

chux - Reinstate Monica 06.06.2024 16:25
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
89
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Например, соглашение о вызовах может гласить, что для определенного функционального интерфейса аргументы 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.

Другие вопросы по теме