Я получил разные результаты для следующего кода с 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
,
между get_3()
и get_4()
является единственной точкой последовательности в printf("%d\n",get_1() + get_2() - (get_3(), get_4()));
вызовы get_x
могут происходить в любом порядке, определенном компилятором, если get_3()
происходит до get_4()
.
Вы видите результат неопределенного поведения.
Спасибо, @StoryTeller, кажется, я редактировал, пока вы комментировали
Это не неопределенное поведение, это неопределенное поведение.
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 дают разные результаты. Вы написали код, который зависит от порядка оценки. В этом нет вины ни одного из компиляторов — нам просто не следует писать программы, основанные на плохо заданном поведении. Если вам нужно выполнять эти функции в определенном порядке, вы должны разделить их на несколько строк/выражений.
Что касается оператора запятая, то это один из редких частных случаев. Он поставляется со встроенной «точкой последовательности», которая гарантирует, что левый операнд всегда оценивается (выполняется) раньше правого. Другими такими особыми случаями являются операторы && ||
и оператор ?:
.
Не совсем в любом порядке. Каждый заказ, в котором
get_4()
происходит раньшеget_3()
, не допускается.