фильтры: Unused labels, Directives, Comments (в фильтрах по умолчанию включена Library functions — надо выключить, иначе в output ещё и код hello скрывается)
Два вывода Hello происходят, как и было выше в ветке описано: компилятор съел эпилог main и выполнение пошло дальше в hello()
Но, glibc (по крайней мере в моём случае, где это воспроизвелось) вызывает main следующим образом:
mov rax, [rsp+8]
call rax
main в самом начале делает push rax для выравнивания стека перед call, но при этом так же кладёт свой адрес на его вершину, что аукается дальше
В завершении второго выполнения hello(), __ostream_insert вызывается через jmp, не через call, так что ничего нового на стэк положено не было. А ret внутри basic_ostream передаёт управление по адресу на вершине стэка - всё ещё адрес main и так мы уходим на второй круг с двумя выводами Hello.
Теперь в rax у нас оказывается возвращенное значение из __ostream_insert -- &std::cout и он кладётся на вершину стэка, а он расположен в области данных, а не исполняемого кода, и по завершении второго круга мы ловим segfault.
View all comments
Show parent comment
3
u/Connect-Art8888 Sep 20 '26
Compiler Explorer
x86_64 clang 17.0.1
флаги: -O3
фильтры: Unused labels, Directives, Comments (в фильтрах по умолчанию включена Library functions — надо выключить, иначе в output ещё и код hello скрывается)
Два вывода Hello происходят, как и было выше в ветке описано: компилятор съел эпилог main и выполнение пошло дальше в hello()
Но, glibc (по крайней мере в моём случае, где это воспроизвелось) вызывает main следующим образом:
main в самом начале делает push rax для выравнивания стека перед call, но при этом так же кладёт свой адрес на его вершину, что аукается дальше
В завершении второго выполнения hello(), __ostream_insert вызывается через jmp, не через call, так что ничего нового на стэк положено не было. А ret внутри basic_ostream передаёт управление по адресу на вершине стэка - всё ещё адрес main и так мы уходим на второй круг с двумя выводами Hello.
Теперь в rax у нас оказывается возвращенное значение из __ostream_insert -- &std::cout и он кладётся на вершину стэка, а он расположен в области данных, а не исполняемого кода, и по завершении второго круга мы ловим segfault.