Що таке розмотування стека?


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

Тепер це дозволяє нам надавати гарантії безпеки виключень .


Це було дійсно просвітливо! Отже, я отримую це: якщо мій процес несподівано зазнає аварійного завершення під час виходу з БУДЬ-якого блоку, в який стек часу вискакував, то може статися, що код за кодом обробника винятків взагалі не буде виконуватися, і це може спричинити витоки пам'яті, купи корупції тощо
Rajendra Uppal,

15
Якщо програма "виходить з ладу" (тобто припиняється через помилку), то будь-яка витік пам'яті або пошкодження купи не мають значення, оскільки пам'ять звільняється при припиненні.
Тайлер Макенрі

1
Саме так. Дякую. Я просто трохи дислексичний сьогодні.
Микола Фетисов

11
@TylerMcHenry: Стандарт не гарантує, що ресурси або пам'ять будуть звільнені після припинення. Однак у більшості ОС це трапляється.
Mooing Duck

3
delete [] pleak;досягається тільки тоді , коли х == 0
Jib

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
читаючи відповіді Миколи, jrista та вашу відповідь у цьому порядку, тепер це має сенс!
n611x007

@sashoalm Ви дійсно вважаєте, що потрібно редагувати публікацію через сім років?
Девід Хольцер

41

У загальному сенсі стек "розмотування" в значній мірі є синонімом закінчення виклику функції та подальшим вискакуванням стека.

Однак, конкретно у випадку C ++, розмотування стека пов'язане з тим, як C ++ викликає деструктори для об'єктів, виділених з моменту запуску будь-якого кодового блоку. Об'єкти, які були створені всередині блоку, розміщуються у зворотному порядку їх розподілу.


4
У tryблоках немає нічого особливого . Об'єкти стеки, виділені в будь-якому блоці (незалежно від того, tryчи ні), підлягають розмотуванню при виході блоку.
Кріс Єстер-Янг

Минув час, так як я зробив багато C ++ кодування. Мені довелося викопати цю відповідь із іржавих глибин. ; P
jrista

не хвилюйся Кожен час від часу має «свої погані».
bitc

13

Розмотування стека - це в основному концепція C ++, яка стосується того, як знищуються виділені стеками об'єкти при виході з його сфери дії (як правило, або через виняток).

Скажімо, у вас є цей фрагмент коду:

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

Чи стосується це будь-якого блоку? Я маю на увазі, якщо є лише {// деякі локальні об’єкти}
Rajendra Uppal,

@Rajendra: Так, анонімний блок визначає область дії, тому він також враховується.
Майкл Майерс

12

Я не знаю, чи читали ви це ще, але стаття Вікіпедії про стек дзвінків має гідне пояснення.

Відмотування:

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

Деякі мови мають інші структури управління, які потребують загального розкручування. Паскаль дозволяє глобальному оператору goto перенести керування з вкладеної функції та у раніше викликану зовнішню функцію. Ця операція вимагає розкручувати стек, видаляючи стільки кадрів стека, скільки потрібно для відновлення належного контексту для передачі управління цільовому оператору в рамках зовнішньої функції, що додається. Аналогічно, C має функції setjmp і longjmp, які діють як не локальні готи. Common Lisp дозволяє контролювати те, що відбувається, коли стек розкручується за допомогою спеціального оператора unind-protection.

Застосовуючи продовження, стек (логічно) розкручується, а потім перемотається зі стеком продовження. Це не єдиний спосіб впровадити продовження; наприклад, використовуючи декілька явних стеків, додаток продовження може просто активувати його стек і запустити передане значення. Мова програмування схеми дозволяє виконувати довільні заклинки у визначених точках на "розмотування" або "перемотування" керуючого стека, коли викликається продовження.

Інспекція [редагувати]


9

Я прочитав пост у блозі, який допоміг мені зрозуміти.

Що таке розмотування стека?

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

Чому б ти хотів це зробити?

Відповідь може здатися очевидною, але є кілька пов'язаних, але тонко різних ситуацій, коли розмотування корисне чи необхідне:

  1. Як механізм контролю потоку виконання (C ++ винятки, C longjmp () тощо).
  2. У відладчику, щоб показати користувачеві стек.
  3. У профілера взяти зразок стека.
  4. Від самої програми (як з обробника аварійного завершення, щоб показати стек).

Вони мають дещо різні вимоги. Деякі з них є критичними для продуктивності, деякі - ні. Деякі вимагають можливості реконструювати регістри із зовнішнього кадру, деякі - ні. Але ми все в цьому розберемося за секунду.

Ви можете знайти повний пост тут .


7

Усі говорили про обробку винятків у C ++. Але, я думаю, є ще одна конотація для розмотування стека, яка пов'язана з налагодженням. Відладчик повинен виконувати розмотування стека всякий раз, коли він повинен перейти до кадру, що передує поточному кадру. Однак це своєрідне віртуальне розмотування, оскільки його потрібно перемотати, коли він повертається до поточного кадру. Прикладом цього можуть бути команди вгору / вниз / bt в gdb.


5
Дію налагодження зазвичай називають "Ходити по стеку", що просто розбирає стек. "Розмотування стека" передбачає не лише "Ходіння по стеку", але і виклик деструкторів об'єктів, які існують у стеці.
Адісак

@Adisak Я не знав, що це також називається "ходіння по стеках". Я завжди бачив "розмотування стека" в контексті всіх статей налагодження, а також навіть у коді gdb. Я вважав, що "розмотування стека" є більш підходящим, оскільки мова йде не лише про зазіхання до інформації про стек для кожної функції, а передбачає розкручування інформації про рамку (див. CFI у карлика). Це обробляється в порядку одна функція за однією.
bbv

Я здогадуюсь, що "ходіння по стеках" Windows стає більш відомим. Крім того, я знайшов в якості прикладу code.google.com/p/google-breakpad/wiki/StackWalking окрім документа доктора- карлика, сам використовує термін розмотування декілька разів. Хоча погодьтеся, це віртуальне розкручування. Більше того, питання, здається, запитує про все можливе значення, яке може запропонувати "розмотування стека".
bbv

7

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

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

На фотографії:

  • Верхня частина - це звичайне виконання дзвінків (без винятку).
  • Знизу один, коли викидається виняток.

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


Діаграми хороші, але пояснення є дещо заплутаним. ... із
Atul,

3

Виконання C ++ знищує всі автоматичні змінні, створені між кидком і ловом. У цьому простому прикладі нижче f1 () кидки та 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

Коли викид виключається і контроль переходить від блоку спробу до обробника, час запуску C ++ викликає деструктори для всіх автоматичних об'єктів, побудованих з початку блоку спробу. Цей процес називається розмотуванням стека. Автоматичні об'єкти руйнуються у зворотному порядку їх побудови. (Автоматичні об'єкти - це локальні об'єкти, які оголошені автоматичними або зареєстрованими, або не оголошені статичними або зовнішніми. Автоматичний об’єкт x видаляється кожного разу, коли програма виходить з блоку, в якому оголошено x.)

Якщо під час побудови об'єкта, що складається з суб'єктів або елементів масиву, викинуто виняток, деструктори викликаються лише для тих суб'єктів або елементів масиву, які були успішно побудовані до викиду винятку. Деструктор для локального статичного об'єкта буде викликатися лише в тому випадку, якщо об'єкт був успішно побудований.


Вам слід надати посилання на початкову статтю, куди ви скопіювали цю відповідь з: База знань IBM - Розмотування стека
w128

0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.