Следующий код,
int foo(int);
int bar(int);
int foobar(int i) {
int a = foo(i);
int b = bar(i);
return a == b ? a : b;
};
с магистралью GCC компилируется в эту сборку:
foobar(int):
push rbx
mov ebx, edi
call foo(int)
mov edi, ebx
pop rbx
jmp bar(int)
Этот TU понятия не имеет, что i
ему будет передано, и он понятия не имеет, какие foo
и bar
вернутся, поэтому он понятия не имеет, будет ли a == b
true
или false
. Так что это должно
проверьте выходные данные двух вызовов и каким-то образом сравните их, чтобы решить, какой из них следует вернуть.
Но я не вижу этого в собрании. Что мне не хватает?
@3CxEZiVlQ, а почему?
если a==b, то возврат a аналогичен возврату b.
@Enlico Подумайте о возможных случаях. У вас есть 2 варианта: a == b
, в этом случае не важно, что вы возвращаете, или a != b
, в этом случае вы возвращаете b
. Компилятор должен вызвать foo
в случае возникновения побочных эффектов, но он может безопасно оптимизировать условие до return b;
, поскольку оно удовлетворяет обоим случаям.
@Evg, я экспериментировал с ссылочной прозрачностью (godbolt.org/z/oqxGbGMMv) и чрезмерно упростил этот пример, поэтому в итоге задал этот вопрос. Если бы я поставил >
вместо ==
, я бы даже не задавал вопрос. :D
@Enlico Хаха, нет... Оооочень умно... я имею в виду компилятор :)
return a == b ? a : b;
то же, что return a == b ? b : b;
то же самое, что return b;
.
Работает с целыми числами... Интересно, что будет с числами с плавающей запятой, где a==b не означает, что a совпадает с b
@Swift-FridayPie достаточно легко увидеть, изменив типы в godbolt. С gcc -fno-signed-zeros
(или -ffast-math
, что подразумевает это) он компилируется идентично версии int, потому что 0
и -0
— единственные числа с плавающей запятой, которые сравниваются равными, но не являются идентичными. Без этого флага вы получите «простую» версию со сравнением и переходом.
clang также делает то же самое с -fno-signed-zeros
, но без него генерирует версию SSE/AVX без ветвей.
Рассмотрим два случая:
(1) a
равно b
.
Тогда foo(i)
и bar(i)
нужно вызывать в этом порядке. Код делает это. Поскольку возвращаемое значение bar(i)
такое же, как и у foo(i)
, достаточно вызвать bar
в хвостовом вызове и позволить его результату вернуться вызвавшему foobar
.
(2) a
не равно b
.
Тогда foo(i)
и bar(i)
нужно вызывать в этом порядке. Код делает это. Поскольку возвращаемое значение bar(i)
отличается от значения foo(i)
, b
, т. е. возвращаемое значение bar(i)
должно быть возвращено вызывающей стороне. Этого можно добиться с помощью хвостового вызова bar
и вернуть результат вызывающему foobar
.
Оба случая делают одно и то же.
Это было бы более полезно, если бы не скрывало (важный) момент хвостового вызова.
@ScottHunter В тексте дважды содержится «хвостовой вызов», не так ли?
Конечно, похоронен среди лишнего текста, поэтому я сказал «непонятно».
Состояние исчезает,
return a == b ? a : b;
оптимизировано доreturn b;