Почему результаты GCC и Clang отличаются от следующего кода? - PullRequest
0 голосов
/ 10 апреля 2019

Я получил разные результаты для следующего кода с 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

Ответы [ 3 ]

8 голосов
/ 10 апреля 2019

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

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

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

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

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

5 голосов
/ 10 апреля 2019

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

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

2 голосов
/ 10 апреля 2019

В игре два разных, но связанных между собой термина: приоритет оператора и порядок вычисления .

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

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

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

  • И, наконец, у нас есть оператор запятой странного числа с наименьшим приоритетом из всех.

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

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

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

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

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

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

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

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

...