Следующий код,
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 == btrue или 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;