Что такое разматывание стека?
Что такое разматывание стека? Обыскал, но не смог найти полезного ответа!
12 ответов
Об размотке стека обычно говорят в связи с обработкой исключений. Вот пример:
void func( int x )
{
char* pleak = new char[1024]; // might be lost => memory leak
std::string s( "hello world" ); // will be properly destructed
if ( x ) throw std::runtime_error( "boom" );
delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
try
{
func( 10 );
}
catch ( const std::exception& e )
{
return 1;
}
return 0;
}
Здесь память выделена для pleak
будет потеряно, если выброшено исключение, а память выделена для s
будет должным образом выпущен std::string
деструктор в любом случае. Объекты, расположенные в стеке, "разматываются" при выходе из области действия (здесь область действия функции func
.) Это делается компилятором, вставляющим вызовы в деструкторы автоматических (стековых) переменных.
Теперь это очень мощная концепция, ведущая к технике, называемой RAII, то есть Resource Acquisition Is Initialization, которая помогает нам управлять такими ресурсами, как память, соединения с базой данных, дескрипторы открытых файлов и т. Д. В C++.
Теперь это позволяет нам предоставлять гарантии безопасности исключений.
Все это относится к C++:
Определение: когда вы создаете объекты статически (в стеке, а не размещаете их в динамической памяти) и выполняете вызовы функций, они "складываются".
Когда область (что-либо ограничено {
а также }
) выходит (используя return XXX;
Достигнув конца области или выбрасывая исключение) все в этой области уничтожается (для всего нужны деструкторы). Этот процесс уничтожения локальных объектов и вызова деструкторов называется разматыванием стека.
У вас есть следующие проблемы, связанные с размоткой стека:
предотвращение утечек памяти (все динамически выделяемое, которое не управляется локальным объектом и не очищается в деструкторе, будет утечкой) - см. RAII, на который ссылается Николай, и документацию для boost::scoped_ptr или этот пример использования boost:: mutex:: scoped_lock.
согласованность программы: спецификации C++ гласят, что вы никогда не должны вызывать исключение до того, как будет обработано любое существующее исключение Это означает, что процесс разматывания стека никогда не должен генерировать исключение (либо использовать только код, гарантированно не выбрасывающий деструкторы, либо окружать все в деструкторах
try {
а также} catch(...) {}
).
Если какой-либо деструктор генерирует исключение во время раскручивания стека, вы попадаете в страну неопределенного поведения, которое может привести к неожиданному завершению работы вашей программы (наиболее распространенное поведение) или к завершению юниверса (теоретически возможно, но пока не наблюдалось на практике).
В общем смысле "раскрутка" стека в значительной степени является синонимом окончания вызова функции и последующего выталкивания стека.
Однако, особенно в случае C++, разматывание стека связано с тем, как C++ вызывает деструкторы для объектов, выделенных с момента запуска любого блока кода. Объекты, созданные в блоке, освобождаются в обратном порядке их размещения.
Я не знаю, читали ли вы это еще, но статья Википедии о стеке вызовов имеет достойное объяснение.
Размотка:
При возврате из вызываемой функции верхний кадр удаляется из стека, возможно, оставляя возвращаемое значение. Более общий процесс выталкивания одного или нескольких кадров из стека для возобновления выполнения в другом месте программы называется разматыванием стека и должен выполняться при использовании нелокальных структур управления, таких как те, которые используются для обработки исключений. В этом случае кадр стека функции содержит одну или несколько записей, определяющих обработчики исключений. Когда выбрасывается исключение, стек разворачивается до тех пор, пока не будет найден обработчик, который готов обработать (перехватить) тип выданного исключения.
Некоторые языки имеют другие структуры управления, которые требуют общего раскручивания. Паскаль позволяет глобальному оператору goto передавать управление из вложенной функции в ранее вызванную внешнюю функцию. Эта операция требует, чтобы стек был размотан, удаляя столько кадров стека, сколько необходимо для восстановления правильного контекста для передачи управления оператору назначения в рамках внешней функции. Точно так же C имеет функции setjmp и longjmp, которые действуют как нелокальные gotos. Common Lisp позволяет контролировать, что происходит, когда стек разматывается, с помощью специального оператора unwind-protect.
При применении продолжения стек (логически) разматывается, а затем перематывается вместе со стеком продолжения. Это не единственный способ реализовать продолжения; например, используя несколько явных стеков, применение продолжения может просто активировать его стек и указать значение для передачи. Язык программирования Scheme позволяет выполнять произвольные последовательности в определенных точках "разматывания" или "перемотки" стека управления при вызове продолжения.
Осмотр [править]
Разматывание стека - это в основном концепция C++, связанная с тем, как объекты, выделенные из стека, уничтожаются при выходе из его области действия (либо обычно, либо через исключение).
Скажем, у вас есть этот фрагмент кода:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
IMO, приведенная ниже диаграмма в этой статье прекрасно объясняет влияние разматывания стека на маршрут следующей инструкции (которая будет выполнена после того, как выброшено исключение, которое не обработано):
На картинке:
- Верхняя часть - это нормальное выполнение вызова (без исключений).
- Нижний, когда выдается исключение.
Во втором случае, когда возникает исключение, в стеке вызовов функций выполняется линейный поиск обработчика исключения. Поиск заканчивается в функции с обработчиком исключений, т.е. main()
с вложением try-catch
блокировать, но не перед удалением всех записей перед ним из стека вызовов функций.
Я прочитал пост в блоге, который помог мне понять.
Что такое разматывание стека?
В любом языке, который поддерживает рекурсивные функции (т. Е. Почти все, кроме Fortran 77 и Brainf*ck), среда выполнения языка хранит стек тех функций, которые выполняются в данный момент. Разматывание стека - это способ проверки и, возможно, изменения этого стека.
Почему вы хотите это сделать?
Ответ может показаться очевидным, но есть несколько связанных, но слегка отличающихся, ситуаций, в которых раскручивание полезно или необходимо:
- Как механизм потока управления во время выполнения (исключения C++, C longjmp() и т. Д.).
- В отладчике, чтобы показать пользователю стек.
- В профилировщике взять образец стека.
- Из самой программы (например, из обработчика сбоя, чтобы показать стек).
У них немного разные требования. Некоторые из них являются критичными для производительности, некоторые нет. Некоторые требуют способности восстанавливать регистры из внешнего кадра, некоторые нет. Но мы разберемся со всем этим через секунду.
Вы можете найти полный пост здесь.
Все говорили об обработке исключений в C++. Но я думаю, что есть и другое значение для раскрутки стека, которое связано с отладкой. Отладчик должен выполнять раскручивание стека всякий раз, когда он должен перейти к кадру, предшествующему текущему кадру. Тем не менее, это своего рода виртуальная раскрутка, так как она должна перематываться, когда возвращается к текущему кадру. Примером этого могут быть команды up/down/bt в gdb.
Среда выполнения C++ уничтожает все автоматические переменные, созданные между throw и catch. В этом простом примере ниже f1() throws и main () перехватывает, между объектами типа B и A создаются в стеке в этом порядке. Когда f1 () выбрасывает, вызывается деструктор B и A.
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A's dtor" << endl; }
};
class B
{
public:
~B() { cout << "B's dtor" << endl; }
};
void f1()
{
B b;
throw (100);
}
void f()
{
A a;
f1();
}
int main()
{
try
{
f();
}
catch (int num)
{
cout << "Caught exception: " << num << endl;
}
return 0;
}
Выход этой программы будет
B's dtor
A's dtor
Это потому, что callstack программы, когда f1 () выбрасывает выглядит
f1()
f()
main()
Таким образом, когда выталкивается f1 (), автоматическая переменная b уничтожается, а затем, когда выталкивается f (), автоматическая переменная a уничтожается.
Надеюсь, это поможет, счастливого кодирования!
Когда генерируется исключение и управление переходит из блока try в обработчик, среда выполнения C++ вызывает деструкторы для всех автоматических объектов, созданных с начала блока try. Этот процесс называется разматыванием стека. Автоматические объекты уничтожаются в порядке, обратном их построению. (Автоматические объекты - это локальные объекты, которые были объявлены как auto или register, или не объявлены как static или extern. Автоматический объект x удаляется всякий раз, когда программа выходит из блока, в котором объявлен x.)
Если исключение выдается во время создания объекта, состоящего из подобъектов или элементов массива, деструкторы вызываются только для тех подобъектов или элементов массива, которые были успешно созданы до того, как было выдано исключение. Деструктор для локального статического объекта будет вызываться только в том случае, если объект был успешно создан.
В Java стеке раскручивание или раскручивание не очень важно (с сборщиком мусора). Во многих работах по обработке исключений я видел эту концепцию (разматывание стека), в особенности эти авторы имеют дело с обработкой исключений в C или C++. с try catch
блоки, которые мы не должны забывать: освободить стек от всех объектов после локальных блоков.
Раскрутка стека - это процесс удаления записей функций из стека вызовов функций во время выполнения. Обычно это связано с обработкой исключений. В C ++ при возникновении исключения в стеке вызовов функций выполняется линейный поиск обработчика исключений, все записи перед тем, как функция с обработчиками исключений удаляется из стека вызовов функций.