Что такое разматывание стека? - PullRequest
181 голосов
/ 25 февраля 2010

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

Ответы [ 11 ]

135 голосов
/ 25 февраля 2010

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

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 , то есть Получение ресурсов - инициализация , которая помогает нам управлять такими ресурсами, как память соединения с базой данных, дескрипторы открытых файлов и т. д. в C ++.

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

64 голосов
/ 25 февраля 2010

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

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

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

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

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

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

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

40 голосов
/ 25 февраля 2010

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

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

13 голосов
/ 25 февраля 2010

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

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

void hw() {
    string hello("Hello, ");
    string world("world!\n");
    cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
11 голосов
/ 25 февраля 2010

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

разматывать:

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

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

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

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

8 голосов
/ 08 мая 2014

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

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

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

Зачем вам это нужно?

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

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

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

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

6 голосов
/ 08 марта 2011

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

4 голосов
/ 01 сентября 2017

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

enter image description here

На картинке:

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

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

3 голосов
/ 03 марта 2014

Среда выполнения 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

Это потому, что стек вызовов программы при броске f1 () выглядит как

f1()
f()
main()

Итак, когда выталкивается f1 (), автоматическая переменная b уничтожается, а затем, когда выталкивается f (), автоматическая переменная a уничтожается.

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

2 голосов
/ 16 апреля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...