Повторные запуски следующей программы C++ дают другое максимальное количество вызовов рекурсии (варьируется примерно на 100 вызовов функций) перед ошибкой сегментации.
#include <iostream>
void recursion(int i)
{
std::cout << "iteration: " << ++i << std::endl;
recursion(i);
}
int main()
{
recursion(0);
};
Я скомпилировал файл main.cpp
с
g++ -O0 main.cpp -o main
Здесь и здесь та же проблема, что и выше, обсуждается для java. В обоих случаях ответы основаны на концепциях, связанных с java, JIT, сборке мусора, оптимизаторе HotSpot и т. д.
Почему максимальное количество рекурсий различается для C++?
Я не знал, что бесконечные рекурсии - это неопределенное поведение в C++, которое объясняет аспект вопроса C++. Но есть некоторые различия в количестве операций, которые ОС «разрешает» перед завершением / уничтожением процесса - по крайней мере, когда стек заполнен?
Это может быть связано с ASLR, попробуйте отключить его и посмотреть, что произойдет.
Отключение ASLR приводит к постоянному числу рекурсий. Это отвечает на вопрос об ОС. Спасибо! Я задокументировал то, что я сделал ниже.
Ваша рекурсия никогда не заканчивается логически. Он завершается только тогда, когда ваша программа падает из-за нехватки места в стеке.
Определенный объем пространства стека используется для каждого рекурсивного вызова, но в C++ точно не определено, сколько пространства стека доступно и сколько используется для каждого рекурсивного вызова.
Пространство стека, используемое для каждого вызова, может варьироваться в зависимости от настроек оптимизации, опций компоновщика, требований к выравниванию, способа запуска вашей программы и множества других вещей.
Итог: вы закодировали ошибку, и вы столкнулись с неопределенным поведением в вашем компиляторе и платформе. Если вы хотите точно определить, сколько места в стеке занимает ваша программа в текущем потоке, ваша платформа будет иметь API-интерфейсы, которые вы можете вызывать для получения этого значения.
но я компилирую его один раз. доступное пространство стека также всегда одинаково. разве выполнение не должно быть независимым от настроек оптимизации и компоновщика для повторного запуска одного и того же двоичного файла? может быть, это не столько вопрос C++, сколько вопрос о том, как операционная система завершает работу программы?
@dgruending • Поддерживает ли ваша платформа ASLR?
@Eljay Eljay Я использую Ubuntu 20.04, поэтому я предполагаю, что ответ на ваш вопрос «да».
«Он завершается только тогда, когда ваша программа дает сбой из-за нехватки места в стеке». Также может достигать UB с переполнением int (и это не только теоретическое, здесь можно легко оптимизировать хвостовую рекурсию).
То, что происходит, когда вы взрываете стек, не является гарантированным сбоем. В зависимости от системы вы можете просто загружать память в относительно случайной части вашего пространства памяти.
То, что находится в этой памяти, может зависеть от того, какие выделения памяти произошли, сколько непрерывной памяти передала вам ОС, когда вы запросили что-то, ASLR или что-то еще.
Неопределенное поведение в C++ непредсказуемо.
Я не знал, что бесконечные циклы являются неопределенным поведением в C++. Связанные N1528
@dgru это не бесконечный цикл. Это неограниченная рекурсия. Реализации C++ могут накладывать ограничения на рекурсию, после чего поведение становится неопределенным (т. е. разрыв стека).
Каким будет процесс, который приведет к «выбрасыванию случайного бита вашей памяти»? Разве ОС не должна это проверять?
@dgruending Пространство памяти процессов. Многие ОС (попытки) защитить систему от процесса, но редко защищают процесс от самого себя. Сбой процесса — пример того, как ОС защищает систему от процесса. Процесс, записывающий свою собственную кучу и приводящий к резкому сбою операций создания/удаления, является примером процесса, наносящего ущерб самому себе. То, как работает эта защита, зависит от системы; размещает ли он стек с огромным набором страниц защиты памяти, или стек переполняется прямо в кучу?
Помимо аспекта C++: после комментариев Eljay и n.'pronouns'.m я отказался от ASLR. Этот пост описывает, как это сделать. Короче говоря, ASLR можно отключить через
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
и включено через
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
После отключения ASLR количество рекурсий до ошибки сегментации системы остается постоянным для повторного выполнения описанной программы.
Неопределенное поведение не определено. Нет ничего необычного в том, что разные прогоны дают разные результаты. Иного вам никто не обещал.