Що таке розмотування стека? Переглянув, але не зміг знайти просвітницьку відповідь!
Що таке розмотування стека? Переглянув, але не зміг знайти просвітницьку відповідь!
Відповіді:
Про розмотування стека зазвичай говорять у зв'язку з обробкою виключень. Ось приклад:
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;
досягається тільки тоді , коли х == 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, які діють як не локальні готи. Common Lisp дозволяє контролювати те, що відбувається, коли стек розкручується за допомогою спеціального оператора unind-protection.
Застосовуючи продовження, стек (логічно) розкручується, а потім перемотається зі стеком продовження. Це не єдиний спосіб впровадити продовження; наприклад, використовуючи декілька явних стеків, додаток продовження може просто активувати його стек і запустити передане значення. Мова програмування схеми дозволяє виконувати довільні заклинки у визначених точках на "розмотування" або "перемотування" керуючого стека, коли викликається продовження.
Інспекція [редагувати]
Я прочитав пост у блозі, який допоміг мені зрозуміти.
Що таке розмотування стека?
На будь-якій мові, яка підтримує рекурсивні функції (тобто майже все, окрім Fortran 77 та Brainf * ck), час виконання мови зберігає стеження тих функцій, які виконуються в даний час. Розмотування стека - це спосіб перевірити та, можливо, змінити цей стек.
Чому б ти хотів це зробити?
Відповідь може здатися очевидною, але є кілька пов'язаних, але тонко різних ситуацій, коли розмотування корисне чи необхідне:
- Як механізм контролю потоку виконання (C ++ винятки, C longjmp () тощо).
- У відладчику, щоб показати користувачеві стек.
- У профілера взяти зразок стека.
- Від самої програми (як з обробника аварійного завершення, щоб показати стек).
Вони мають дещо різні вимоги. Деякі з них є критичними для продуктивності, деякі - ні. Деякі вимагають можливості реконструювати регістри із зовнішнього кадру, деякі - ні. Але ми все в цьому розберемося за секунду.
Ви можете знайти повний пост тут .
Усі говорили про обробку винятків у C ++. Але, я думаю, є ще одна конотація для розмотування стека, яка пов'язана з налагодженням. Відладчик повинен виконувати розмотування стека всякий раз, коли він повинен перейти до кадру, що передує поточному кадру. Однак це своєрідне віртуальне розмотування, оскільки його потрібно перемотати, коли він повертається до поточного кадру. Прикладом цього можуть бути команди вгору / вниз / bt в gdb.
IMO, наведена нижче діаграма в цій статті прекрасно пояснює ефект розмотування стека на маршруті наступної інструкції (виконується після того, як буде викинуто виняток, який є нездійсненим):
На фотографії:
У другому випадку, коли відбувається виняток, стек виклику функції лінійно шукається для обробника винятку. Пошук закінчується у функції за допомогою обробника винятків, тобто блоку, main()
що try-catch
вкладається, але не перед тим, як видалити всі записи, що передували йому, із стека виклику функції.
Виконання 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, знищується.
Сподіваюся, це допомагає, щасливе кодування!
Коли викид виключається і контроль переходить від блоку спробу до обробника, час запуску C ++ викликає деструктори для всіх автоматичних об'єктів, побудованих з початку блоку спробу. Цей процес називається розмотуванням стека. Автоматичні об'єкти руйнуються у зворотному порядку їх побудови. (Автоматичні об'єкти - це локальні об'єкти, які оголошені автоматичними або зареєстрованими, або не оголошені статичними або зовнішніми. Автоматичний об’єкт x видаляється кожного разу, коли програма виходить з блоку, в якому оголошено x.)
Якщо під час побудови об'єкта, що складається з суб'єктів або елементів масиву, викинуто виняток, деструктори викликаються лише для тих суб'єктів або елементів масиву, які були успішно побудовані до викиду винятку. Деструктор для локального статичного об'єкта буде викликатися лише в тому випадку, якщо об'єкт був успішно побудований.
У Java стеки розмотування чи розмотування не дуже важливі (зі збирачем сміття). У багатьох документах, що займаються обробкою винятків, я бачив цю концепцію (розмотування стека), у спеціальних написальних документах йдеться про обробку винятків у C або C ++. з try catch
блоками ми не повинні забувати: звільняємо стек від усіх об'єктів після локальних блоків .