Встроенные и оптимизированные оконечные вызовы - это два отдельных способа, чтобы вызовы в источнике C не появлялись в обратном следе стека времени выполнения.
Встроенная функция приведет к тому, что вызовы ее дочерних элементов будут происходить непосредственно из родительский элемент, в который он встроен, причем эта функция вообще не отображается.
Разумеется, возможно несколько уровней встраивания, что характерно для небольших функций.
Функция, заканчивающаяся на Оптимизированный jmp
tailcall заставляет его выглядеть так, как будто вызываемый абонент был вызван непосредственно из его родителя. Вместо создания нового стекового фрейма ниже своего собственного, он разбирает свой собственный стековый фрейм и переходит к целевой функции, а адрес возврата все еще там. Таким образом, стековый фрейм новой функции будет использовать ту же память, которая ранее использовалась функцией, которая выполняла вызов хвоста.
Другими словами, любая последовательность call func; ret
может быть заменена на jmp func
. Это работает, даже если целью jmp является (заглушка plt для) библиотечной функции или косвенная цель call/jmp *printf@GOTPCREL(%rip)
для кодекса стиля gcc -fno-plt
.
См. Также https://en.wikipedia.org/wiki/Tail_call
например,
void leaf(); // some other function
void foo() { stuff; ...; leaf(); } // ends with a tailcall
void bar() { stuff; foo(); stuff; }
Событие, которое происходит, пока foo
делает "вещи", будет иметь обратную трассировку bar-> foo.
Без оптимизации, событие у стрельбы в "leaf" будет обратная трассировка, такая как bar-> foo-> leaf.
При оптимизированном обратном вызове это будет bar-> leaf, а не foo, потому что foo просто перепрыгнул на leaf, направив его возврат обратиться к leaf
, поэтому, когда leaf
в конечном итоге вернется, оно будет непосредственно к bar
.
Это работает с аргументами и возвращаемыми значениями, особенно с регистрами аргументов. Не всегда с аргументами стека, например, это невозможно, если leaf
имеет больше аргументов стека, чем foo
.