Что такое разматывание стека? Обыскал, но не смог найти поучительного ответа!
Что такое разматывание стека? Обыскал, но не смог найти поучительного ответа!
Ответы:
Об размотке стека обычно говорят в связи с обработкой исключений. Вот пример:
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 ++.
Теперь это позволяет нам предоставлять гарантии безопасности исключений .
delete [] pleak;
достигается только если x == 0.
Все это относится к C ++:
Определение : когда вы создаете объекты статически (в стеке, а не размещаете их в динамической памяти) и выполняете вызовы функций, они «складываются».
При выходе из области действия (что-либо, ограниченное символами {
и }
) (с помощью return XXX;
достижения конца области действия или создания исключения) все содержимое этой области уничтожается (для всего вызывается деструктор). Этот процесс уничтожения локальных объектов и вызова деструкторов называется разматыванием стека.
У вас есть следующие проблемы, связанные с размоткой стека:
предотвращение утечек памяти (что-либо динамически размещаемое, которое не управляется локальным объектом и не очищается в деструкторе, будет утечка) - см. RAII, на который ссылается Николай, и документацию для boost :: scoped_ptr или этот пример использования boost :: mutex :: scoped_lock .
согласованность программы: спецификации C ++ гласят, что вы никогда не должны вызывать исключение до того, как будет обработано любое существующее исключение. Это означает, что процесс разматывания стека никогда не должен генерировать исключение (либо использовать только код, гарантированно не выбрасывающий деструкторы, либо окружать все в деструкторах с помощью try {
и } catch(...) {}
).
Если какой-либо деструктор генерирует исключение во время раскручивания стека, вы попадаете в страну неопределенного поведения, которое может привести к неожиданному завершению вашей программы (наиболее распространенное поведение) или к завершению юниверса (теоретически возможно, но пока не наблюдалось на практике).
В общем смысле «раскрутка» стека в значительной степени является синонимом окончания вызова функции и последующего выталкивания стека.
Однако, в частности, в случае C ++, разматывание стека связано с тем, как C ++ вызывает деструкторы для объектов, выделенных с момента запуска любого блока кода. Объекты, созданные в блоке, освобождаются в обратном порядке их размещения.
try
блоках нет ничего особенного . Объекты стека, выделенные в любом блоке (независимо от того, try
есть), подлежат размотке при выходе из блока.
Разматывание стека - это в основном концепция C ++, связанная с тем, как объекты, выделенные из стека, уничтожаются при выходе из области видимости (как обычно, так и через исключение).
Скажем, у вас есть этот фрагмент кода:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Я не знаю, читали ли вы это еще, но статья Википедии о стеке вызовов имеет достойное объяснение.
Развернув:
Возврат из вызванной функции вытолкнет верхний кадр из стека, возможно, оставив возвращаемое значение. Более общий процесс выталкивания одного или нескольких кадров из стека для возобновления выполнения в другом месте программы называется разматыванием стека и должен выполняться при использовании нелокальных структур управления, таких как те, которые используются для обработки исключений. В этом случае стековый фрейм функции содержит одну или несколько записей, определяющих обработчики исключений. Когда выбрасывается исключение, стек разворачивается до тех пор, пока не будет найден обработчик, который готов обработать (перехватить) тип выданного исключения.
Некоторые языки имеют другие структуры управления, которые требуют общего раскручивания. Паскаль позволяет глобальному оператору goto передавать управление из вложенной функции в ранее вызванную внешнюю функцию. Эта операция требует, чтобы стек был размотан, удаляя столько кадров стека, сколько необходимо для восстановления надлежащего контекста, чтобы передать управление оператору назначения в рамках внешней функции. Точно так же C имеет функции setjmp и longjmp, которые действуют как нелокальные gotos. Common Lisp позволяет контролировать, что происходит, когда стек разматывается, с помощью специального оператора unwind-protect.
При применении продолжения стек (логически) разматывается, а затем перематывается вместе со стеком продолжения. Это не единственный способ реализации продолжений; например, используя несколько явных стеков, применение продолжения может просто активировать его стек и указать значение для передачи. Язык программирования Scheme позволяет выполнять произвольные последовательности в определенных точках «разматывания» или «перемотки» стека управления при вызове продолжения.
Осмотр [править]
Я прочитал пост в блоге, который помог мне понять.
Что такое разматывание стека?
В любом языке, который поддерживает рекурсивные функции (т. Е. Почти все, кроме Fortran 77 и Brainf * ck), среда исполнения языка хранит стек тех функций, которые выполняются в данный момент. Разматывание стека - это способ проверки и, возможно, изменения этого стека.
Почему вы хотите это сделать?
Ответ может показаться очевидным, но есть несколько связанных, но слегка отличающихся друг от друга ситуаций, в которых раскручивание полезно или необходимо:
- Как механизм потока управления во время выполнения (исключения C ++, C longjmp () и т. Д.).
- В отладчике, чтобы показать пользователю стек.
- В профилировщике взять образец стека.
- Из самой программы (как из обработчика сбоя, чтобы показать стек).
У них немного другие требования. Некоторые из них являются критичными для производительности, некоторые нет. Некоторые требуют способности восстанавливать регистры из внешнего кадра, некоторые - нет. Но мы разберемся со всем этим через секунду.
Вы можете найти полный пост здесь .
Все говорили об обработке исключений в C ++. Но, я думаю, есть и другое значение для раскрутки стека, и это связано с отладкой. Отладчик должен выполнять раскручивание стека всякий раз, когда предполагается перейти к кадру, предшествующему текущему кадру. Тем не менее, это своего рода виртуальное раскручивание, так как его необходимо перематывать, когда оно возвращается к текущему кадру. Примером этого могут быть команды up / down / bt в gdb.
IMO, приведенная ниже диаграмма в этой статье прекрасно объясняет эффект разматывания стека на маршруте следующей инструкции (которая должна быть выполнена, когда выброшено исключение, которое не обработано):
На картинке:
Во втором случае, когда возникает исключение, в стеке вызовов функций выполняется линейный поиск обработчика исключения. Поиск заканчивается в функции с обработчиком исключений, то есть main()
с включающим try-catch
блоком, но не перед удалением всех записей перед ним из стека вызовов функции.
Среда выполнения 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
блоками мы не должны забывать: свободный стек от всех объектов после локальных блоков .