#c #gcc #llvm
#c #gcc #llvm
Вопрос:
Я получил разные результаты для следующего кода с gcc и clang, я считаю, что это не серьезная ошибка, но мне интересно, какой результат более соответствует стандарту? Большое спасибо за ваш ответ.
Я использую gcc (Ubuntu 7.3.0-27ubuntu1 ~ 18.04) 7.3.0 и clang версии 6.0.0-1ubuntu2 (tags / RELEASE_600 / final)
#include <stdio.h>
int get_1(){
printf("get_1n");
return 1;
}
int get_2(){
printf("get_2n");
return 2;
}
int get_3(){
printf("get_3n");
return 3;
}
int get_4(){
printf("get_4n");
return 4;
}
int main(int argc, char *argv[])
{
printf("%dn",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
Ответ №1:
C не устанавливает порядок при вычислении операндов некоторых операторов. Порядок вычисления определяется в стандарте C точками последовательности. Когда у вас есть точки последовательности, правильная реализация языка должна завершить оценку всего, что находится слева от точки последовательности, прежде чем она начнет оценивать то, что присутствует в правой части. Операторы
and -
не содержат никакой точки последовательности. Вот само определение из 5.1.2.3 p2
В определенных заданных точках последовательности выполнения, называемых точками последовательности, все побочные эффекты предыдущих оценок должны быть полными, и никаких побочных эффектов последующих оценок не должно быть.
В вашем выражении
get_1() get_2() - (get_3(), get_4())
у вас есть
, -
и оператор запятой ,
. Только запятая устанавливает порядок вычисления,
а -
and — нет.
Ответ №2:
,
Между get_3()
и get_4()
является единственной точкой последовательности в printf("%dn",get_1() get_2() - (get_3(), get_4()));
get_x
вызовах, которые могут выполняться в любом порядке, определенном компилятором, если get_3()
это происходит раньше get_4()
.
Вы видите результат неопределенного поведения.
Комментарии:
1. Не совсем в любом порядке. Каждый порядок, где
get_4()
происходит раньшеget_3()
, не допускается.2. Спасибо @StoryTeller, я думаю, что редактировал, пока вы комментировали
3. Это не неопределенное поведение, это неопределенное поведение.
Ответ №3:
В игре используются два разных, но связанных термина: приоритет оператора и порядок вычисления.
Приоритет оператора определяет порядок синтаксического анализа:
-
В вашем выражении скобка имеет наивысший приоритет, поэтому то, что внутри нее, принадлежит вместе.
-
Далее у нас есть операторы вызова функций
()
. В этом нет ничего странного, они являются постфиксными и принадлежат их оператору, имени функции. -
Далее у нас есть бинарные
-
операторы и . Они принадлежат к одной и той же группе операторов «аддитивные операторы» и имеют одинаковый приоритет. Когда это происходит, ассоциативность операторов для операторов этой группы решает, в каком порядке они должны быть проанализированы.Для аддитивных операторов ассоциативность операторов слева направо. Это означает, что выражение гарантированно будет проанализировано как
(get_1() get_2()) - ...
. -
И, наконец, у нас есть странный оператор запятой с наименьшим приоритетом из всех.
Как только приоритет оператора отсортирован, как указано выше, мы знаем, какие операнды принадлежат каким операторам. Но это ничего не говорит о том, в каком порядке будет выполняться выражение. Вот где порядок оценки.
Обычно C говорит в сухих стандартных терминах:
За исключением случаев, указанных ниже, побочные эффекты и вычисления значений подвыражений не упорядочены.
На простом английском это означает, что порядок вычисления операндов по большей части не определен, за некоторыми особыми исключениями.
Для аддитивных операторов
и -
это верно. Учитывая a b
, что мы не можем знать, будет ли a
или b
будет выполнен первым. Порядок вычисления не указан — компилятор может выполнять его в любом порядке, который ему нравится, не нужно документировать, как это делается, и даже не нужно вести себя последовательно от случая к случаю.
Это намеренно не указано стандартом C, чтобы позволить разным компиляторам анализировать выражения по-разному. По сути, это позволяет им сохранять свой алгоритм дерева выражений в коммерческой тайне компилятора, чтобы позволить некоторым компиляторам создавать более эффективный код, чем другие на свободном рынке.
И именно поэтому gcc и clang дают разные результаты. Вы написали код, который зависит от порядка вычисления. Это не ошибка ни одного из компиляторов — мы просто не должны писать программы, которые полагаются на плохо определенное поведение. Если вам нужно выполнить эти функции в определенном порядке, вы должны разделить их на несколько строк / выражений.
Что касается оператора запятой, это один из редких особых случаев. Он поставляется со встроенной «точкой последовательности», которая гарантирует, что левый операнд всегда вычисляется (выполняется) перед правым. Другими такими частными случаями являются amp;amp; ||
операторы и ?:
оператор.