Почему результаты GCC и Clang отличаются для следующего кода?

Я получил разные результаты для следующего кода с gcc и clang, я считаю, что это не серьезная ошибка, но мне интересно, какой результат более соответствует стандарту? Большое спасибо за ваш ответ.

Я использую gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 и clang версии 6.0.0-1ubuntu2 (теги/RELEASE_600/final)

#include <stdio.h>
int get_1(){
        printf("get_1\n");
        return 1;
}
int get_2(){
        printf("get_2\n");
        return 2;
}
int get_3(){
        printf("get_3\n");
        return 3;
}
int get_4(){
        printf("get_4\n");
        return 4;
}
int main(int argc, char *argv[])
{
        printf("%d\n",get_1() + get_2() - (get_3(), get_4()));
        return 0;
}

результат gcc

get_3
get_1
get_2
get_4
-1

и результат clang

get_1
get_2
get_3
get_4
-1
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
0
340
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

, между get_3() и get_4() является единственной точкой последовательности в printf("%d\n",get_1() + get_2() - (get_3(), get_4())); вызовы get_x могут происходить в любом порядке, определенном компилятором, если get_3() происходит до get_4().

Вы видите результат неопределенного поведения.

Не совсем в любом порядке. Каждый заказ, в котором get_4() происходит раньше get_3(), не допускается.

StoryTeller - Unslander Monica 10.04.2019 12:36

Спасибо, @StoryTeller, кажется, я редактировал, пока вы комментировали

Colin 10.04.2019 12:36

Это не неопределенное поведение, это неопределенное поведение.

mch 10.04.2019 12:37
Ответ принят как подходящий

C не навязывает порядок вычисления операндов некоторых операторов. Порядок оценки определяется в стандарте C точки последовательности. Когда у вас есть точки следования, правильная реализация языка должна завершить оценку всего слева от точки следования, прежде чем она начнет оценивать то, что присутствует справа. Операторы + и - не содержат точек следования. Вот само определение от 5.1.2.3 п2

At certain specified points in the execution sequence called sequence points,all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.

В вашем выражении

get_1() + get_2() - (get_3(), get_4())

у вас есть оператор +, - и запятая ,. Только запятая определяет порядок оценки, а + и - - нет.

В игре есть два разных, но связанных термина: приоритет оператора и порядок оценки.

Приоритет оператора определяет порядок синтаксического анализа:

  • В вашем выражении скобка имеет наивысший приоритет, поэтому то, что внутри нее, принадлежит друг другу.

  • Далее у нас есть операторы вызова функций (). Ничего странного, они постфиксные и принадлежат своему оператору, имени функции.

  • Далее у нас есть бинарные операторы + и -. Они принадлежат к одной и той же группе операторов «аддитивные операторы» и имеют одинаковый приоритет. Когда это происходит, ассоциативность операторов для операторов этой группы решает, в каком порядке их следует анализировать.

    Для аддитивных операторов ассоциативность операторов слева направо. Это означает, что выражение гарантированно будет проанализировано как (get_1() + get_2()) - ....

  • И, наконец, у нас есть странный оператор запятой с самым низким приоритетом.

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

Обычно C говорит в сухих стандартных терминах:

Except as specified later, side effects and value computations of subexpressions are unsequenced.

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

Для аддитивных операторов + и - это верно. Учитывая a + b, мы не можем знать, будет ли a или b выполнено первым. Порядок оценки не определен - компилятор может выполнять его в любом порядке, который ему нравится, не нужно документировать, как, и даже не нужно вести себя последовательно от случая к случаю.

Это намеренно не указано в стандарте C, чтобы разные компиляторы могли по-разному анализировать выражения. По сути, это позволяет им сохранить свой алгоритм дерева выражений в коммерческой тайне компилятора, чтобы позволить некоторым компиляторам создавать более эффективный код, чем другие на свободном рынке.

Вот почему gcc и clang дают разные результаты. Вы написали код, который зависит от порядка оценки. В этом нет вины ни одного из компиляторов — нам просто не следует писать программы, основанные на плохо заданном поведении. Если вам нужно выполнять эти функции в определенном порядке, вы должны разделить их на несколько строк/выражений.

Что касается оператора запятая, то это один из редких частных случаев. Он поставляется со встроенной «точкой последовательности», которая гарантирует, что левый операнд всегда оценивается (выполняется) раньше правого. Другими такими особыми случаями являются операторы && || и оператор ?:.

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