2016-10-07 3 views
6

Я думал, я понимаю, как точки последовательности работают в C++, но this GeeksQuiz question меня озадачило:Вызов функции с побочными эффектами внутри выражения

int f(int &x, int c) { 
    c = c - 1; 
    if (c == 0) return 1; 
    x = x + 1; 
    return f(x, c) * x; 
} 

int main() { 
    int p = 5; 
    cout << f(p, p) << endl; 
    return 0; 
} 

«Правильный» ответ на этот вопрос говорит, что печатает 6561. Действительно, в VS2013 оно делает. Но разве это не UB, потому что нет гарантии, которая будет оценена первой: f(x, c) или x. Мы получим 6561, если сначала оценивается f(x, c): все это превращается в пять рекурсивных вызовов: первые четыре (c = 5, 4, 3, 2) продолжаются, последний (c = 1) завершается и возвращает 1, что в конце концов составляет 9 ** 4.

Однако, если сначала оценивали x, тогда мы бы получили 6 * 7 * 8 * 9 * 1. Самое смешное, что в VS2013 даже замена f(x, c) * x на x * f(x, c) не изменит результат. Не значит, что это что-то значит.

В соответствии со стандартом, является это UB или нет? Если нет, то почему?

+2

clang дает '3024' для' x * f (x, c) '(и' 6561' для 'f (x, c) * x'). – Holt

ответ

4

Это UB.

n4140 §1.9 [intro.execution]/15

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

Умножительные операторы не имеют четко определенного положения.

2

Это UB

Порядок оценки операндов почти всех C++ операторов (в том числе порядок вычисления аргументов функции в выражении вызова функции и порядок оценки подвыражения в любом выражении) неуточнен. Компилятор может оценивать операнды в любом порядке и может выбирать другой порядок, когда одно и то же выражение оценивается снова.

Существуют исключения из этого правила, которые указаны ниже.

За исключением случаев, отмеченных ниже, нет концепции слева направо или оценки справа налево в C++. Это не следует путать с ассоциацией операторов слева-направо и справа налево: выражение f1() + f2() + f3() анализируется как (f1() + f2()) + f3() из-за ассоциативность слева-направо оператора +, но вызов функции f3 может быть оценен сначала, последним или между f1() или f2() во время выполнения.