Что такое разматывание стека?


193

Что такое разматывание стека? Обыскал, но не смог найти поучительного ответа!


76
Если он не знает, что это такое, как вы можете ожидать, что он узнает, что они не одинаковы для C и C ++?
Dreamlax

@dreamlax: Итак, чем отличается концепция «разматывания стека» в C & C ++?
Разрушитель

2
@PravasiMeet: C не имеет обработки исключений, поэтому разматывание стека происходит очень просто, однако в C ++, если выбрасывается исключение или происходит выход из функции, разматывание стека включает в себя уничтожение любых объектов C ++ с автоматическим хранением.
Dreamlax

Ответы:


150

Об размотке стека обычно говорят в связи с обработкой исключений. Вот пример:

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 ++.

Теперь это позволяет нам предоставлять гарантии безопасности исключений .


Это было действительно поучительно! Итак, я получаю следующее: если мой процесс неожиданно завершился с ошибкой во время выхода из ЛЮБОГО блока, в котором был извлечен стек времени, то может случиться так, что код после кода обработчика исключений вообще не будет выполняться, и это может вызвать утечки памяти, коррупция в куче и т.д.
Раджендра Уппал

15
Если программа «вылетает» (то есть завершается из-за ошибки), то любая утечка памяти или повреждение кучи не имеет значения, так как память освобождается при завершении.
Тайлер МакГенри

1
Именно. Спасибо. Я просто немного болею сегодня.
Николай Фетисов

11
@TylerMcHenry: Стандарт не гарантирует освобождение ресурсов или памяти при завершении. Однако большинство ОС делают это.
Mooing Duck

3
delete [] pleak;достигается только если x == 0.
Стрела

71

Все это относится к C ++:

Определение : когда вы создаете объекты статически (в стеке, а не размещаете их в динамической памяти) и выполняете вызовы функций, они «складываются».

При выходе из области действия (что-либо, ограниченное символами {и }) (с помощью return XXX;достижения конца области действия или создания исключения) все содержимое этой области уничтожается (для всего вызывается деструктор). Этот процесс уничтожения локальных объектов и вызова деструкторов называется разматыванием стека.

У вас есть следующие проблемы, связанные с размоткой стека:

  1. предотвращение утечек памяти (что-либо динамически размещаемое, которое не управляется локальным объектом и не очищается в деструкторе, будет утечка) - см. RAII, на который ссылается Николай, и документацию для boost :: scoped_ptr или этот пример использования boost :: mutex :: scoped_lock .

  2. согласованность программы: спецификации C ++ гласят, что вы никогда не должны вызывать исключение до того, как будет обработано любое существующее исключение. Это означает, что процесс разматывания стека никогда не должен генерировать исключение (либо использовать только код, гарантированно не выбрасывающий деструкторы, либо окружать все в деструкторах с помощью try {и } catch(...) {}).

Если какой-либо деструктор генерирует исключение во время раскручивания стека, вы попадаете в страну неопределенного поведения, которое может привести к неожиданному завершению вашей программы (наиболее распространенное поведение) или к завершению юниверса (теоретически возможно, но пока не наблюдалось на практике).


2
Напротив. Хотя gotos не следует злоупотреблять, они вызывают раскручивание стека в MSVC (не в GCC, поэтому, вероятно, это расширение). setjmp и longjmp делают это кросс-платформенным способом, с несколько меньшей гибкостью.
Патрик Недзельски

10
Я только что проверил это с помощью gcc, и он правильно вызывает деструкторы, когда вы выходите из блока кода. См. Stackoverflow.com/questions/334780/… - как уже упоминалось в этой ссылке, это также является частью стандарта.
Дамян

1
читая слова Николая, Ирисы и ваш ответ в таком порядке, теперь это имеет смысл!
n611x007

@sashoalm Ты действительно считаешь необходимым редактировать пост семь лет спустя?
Дэвид Хоэлзер

41

В общем смысле «раскрутка» стека в значительной степени является синонимом окончания вызова функции и последующего выталкивания стека.

Однако, в частности, в случае C ++, разматывание стека связано с тем, как C ++ вызывает деструкторы для объектов, выделенных с момента запуска любого блока кода. Объекты, созданные в блоке, освобождаются в обратном порядке их размещения.


4
В tryблоках нет ничего особенного . Объекты стека, выделенные в любом блоке (независимо от того, tryесть), подлежат размотке при выходе из блока.
Крис Джестер-Янг

Уже давно я много занимался кодированием на C ++. Мне пришлось выкопать этот ответ из ржавых глубин. ; П
ириста

не волнуйся У каждого бывает "свое плохое" время от времени.
Бит

13

Разматывание стека - это в основном концепция C ++, связанная с тем, как объекты, выделенные из стека, уничтожаются при выходе из области видимости (как обычно, так и через исключение).

Скажем, у вас есть этот фрагмент кода:

void hw() {
    string hello("Hello, ");
    string world("world!\n");
    cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"

Это относится к любому блоку? Я имею в виду, если есть только {// некоторые локальные объекты}
Раджендра Уппал

@Rajendra: Да, анонимный блок определяет область действия, поэтому он тоже имеет значение.
Майкл Майерс

12

Я не знаю, читали ли вы это еще, но статья Википедии о стеке вызовов имеет достойное объяснение.

Развернув:

Возврат из вызванной функции вытолкнет верхний кадр из стека, возможно, оставив возвращаемое значение. Более общий процесс выталкивания одного или нескольких кадров из стека для возобновления выполнения в другом месте программы называется разматыванием стека и должен выполняться при использовании нелокальных структур управления, таких как те, которые используются для обработки исключений. В этом случае стековый фрейм функции содержит одну или несколько записей, определяющих обработчики исключений. Когда выбрасывается исключение, стек разворачивается до тех пор, пока не будет найден обработчик, который готов обработать (перехватить) тип выданного исключения.

Некоторые языки имеют другие структуры управления, которые требуют общего раскручивания. Паскаль позволяет глобальному оператору goto передавать управление из вложенной функции в ранее вызванную внешнюю функцию. Эта операция требует, чтобы стек был размотан, удаляя столько кадров стека, сколько необходимо для восстановления надлежащего контекста, чтобы передать управление оператору назначения в рамках внешней функции. Точно так же C имеет функции setjmp и longjmp, которые действуют как нелокальные gotos. Common Lisp позволяет контролировать, что происходит, когда стек разматывается, с помощью специального оператора unwind-protect.

При применении продолжения стек (логически) разматывается, а затем перематывается вместе со стеком продолжения. Это не единственный способ реализации продолжений; например, используя несколько явных стеков, применение продолжения может просто активировать его стек и указать значение для передачи. Язык программирования Scheme позволяет выполнять произвольные последовательности в определенных точках «разматывания» или «перемотки» стека управления при вызове продолжения.

Осмотр [править]


9

Я прочитал пост в блоге, который помог мне понять.

Что такое разматывание стека?

В любом языке, который поддерживает рекурсивные функции (т. Е. Почти все, кроме Fortran 77 и Brainf * ck), среда исполнения языка хранит стек тех функций, которые выполняются в данный момент. Разматывание стека - это способ проверки и, возможно, изменения этого стека.

Почему вы хотите это сделать?

Ответ может показаться очевидным, но есть несколько связанных, но слегка отличающихся друг от друга ситуаций, в которых раскручивание полезно или необходимо:

  1. Как механизм потока управления во время выполнения (исключения C ++, C longjmp () и т. Д.).
  2. В отладчике, чтобы показать пользователю стек.
  3. В профилировщике взять образец стека.
  4. Из самой программы (как из обработчика сбоя, чтобы показать стек).

У них немного другие требования. Некоторые из них являются критичными для производительности, некоторые нет. Некоторые требуют способности восстанавливать регистры из внешнего кадра, некоторые - нет. Но мы разберемся со всем этим через секунду.

Вы можете найти полный пост здесь .


7

Все говорили об обработке исключений в C ++. Но, я думаю, есть и другое значение для раскрутки стека, и это связано с отладкой. Отладчик должен выполнять раскручивание стека всякий раз, когда предполагается перейти к кадру, предшествующему текущему кадру. Тем не менее, это своего рода виртуальное раскручивание, так как его необходимо перематывать, когда оно возвращается к текущему кадру. Примером этого могут быть команды up / down / bt в gdb.


5
Действие отладчика обычно называется «Stack Walking», которое просто анализирует стек. «Разматывание стека» подразумевает не только «Прогулку по стеку», но и вызов деструкторов объектов, существующих в стеке.
Adisak

@ Adisak Я не знал, что это также называется "стековая ходьба". Я всегда видел «раскручивание стека» в контексте всех статей отладчика, а также даже внутри кода GDB. Я чувствовал, что «разматывание стека» более уместно, поскольку речь идет не только о просмотре информации стека для каждой функции, но и о разматывании информации о кадре (ср. CFI в карлике). Это обрабатывается по порядку одна функция за одной.
BBV

Я предполагаю, что "ходьба по стеку" сделана более известной Windows. Кроме того, я нашел в качестве примера code.google.com/p/google-breakpad/wiki/StackWalking, помимо самого документа dwarf standard, в котором термин разматывание используется несколько раз. Хотя согласитесь, это виртуальное раскручивание. Кроме того, вопрос, кажется, задает все возможные значения, которые может предложить «раскрутка стека».
BBV

7

IMO, приведенная ниже диаграмма в этой статье прекрасно объясняет эффект разматывания стека на маршруте следующей инструкции (которая должна быть выполнена, когда выброшено исключение, которое не обработано):

введите описание изображения здесь

На картинке:

  • Верхняя часть - это обычное выполнение вызова (без исключений).
  • Внизу, когда выдается исключение.

Во втором случае, когда возникает исключение, в стеке вызовов функций выполняется линейный поиск обработчика исключения. Поиск заканчивается в функции с обработчиком исключений, то есть main()с включающим try-catchблоком, но не перед удалением всех записей перед ним из стека вызовов функции.


Диаграммы хороши, но объяснение немного сбивает с толку, а именно. ... с включенным блоком try-catch, но не перед удалением всех записей перед ним из стека вызовов функций ...
Atul

3

Среда выполнения 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 уничтожается.

Надеюсь, это поможет, счастливого кодирования!


2

Когда генерируется исключение и управление переходит от блока try к обработчику, среда выполнения C ++ вызывает деструкторы для всех автоматических объектов, созданных с начала блока try. Этот процесс называется разматыванием стека. Автоматические объекты уничтожаются в порядке, обратном их построению. (Автоматические объекты - это локальные объекты, которые были объявлены как auto или register, или не объявлены как static или extern. Автоматический объект x удаляется всякий раз, когда программа выходит из блока, в котором объявлен x.)

Если исключение выдается во время создания объекта, состоящего из подобъектов или элементов массива, деструкторы вызываются только для тех подобъектов или элементов массива, которые были успешно созданы до того, как было выдано исключение. Деструктор для локального статического объекта будет вызываться только в том случае, если объект был успешно создан.


Вы должны предоставить ссылку на оригинальную статью, в которую вы скопировали этот ответ: База знаний IBM
Разматывание

0

В стеке Java раскручивание или разматывание не очень важно (с сборщиком мусора). Во многих работах по обработке исключений я видел эту концепцию (разматывание стека), в частности эти авторы имеют дело с обработкой исключений в C или C ++. с try catchблоками мы не должны забывать: свободный стек от всех объектов после локальных блоков .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.