Что такое раскручивание стека?

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

Ответ 1

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

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 будет потеряна, если будет pleak исключение, а память, выделенная для s будет должным образом освобождена деструктором std::string в любом случае. Объекты, расположенные в стеке, "разматываются" при выходе из области действия (здесь область действия функции func.) Это делается компилятором, вставляющим вызовы деструкторов автоматических (стековых) переменных.

Теперь это очень мощная концепция, ведущая к технике под названием RAII, то есть Resource Acquisition Is Initialization, которая помогает нам управлять такими ресурсами, как память, соединения с базой данных, дескрипторы открытых файлов и т.д. В C++.

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

Inspection [править]

Ответ 6

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

Что такое стирание стека?

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

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

В pic:

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

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

Ответ 9

Среда выполнения С++ уничтожает все автоматические переменные, созданные между броском и catch. В этом простом примере ниже f1() throw и main() уловы между объектами типа B и A создаются в стеке в этом порядке. Когда вызывается f1(), вызывается деструкторы B и A.

#include <iostream>
using namespace std;

class A
{
    public:
       ~A() { cout << "A dtor" << endl; }
};

class B
{
    public:
       ~B() { cout << "B 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 dtor
A dtor

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

f1()
f()
main()

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

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

Ответ 10

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

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

Ответ 11

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