กองคลี่คลายคืออะไร? ค้นหาผ่าน แต่ไม่พบคำตอบที่กระจ่างแจ้ง!
กองคลี่คลายคืออะไร? ค้นหาผ่าน แต่ไม่พบคำตอบที่กระจ่างแจ้ง!
คำตอบ:
การคลายสแตกนั้นมักจะพูดถึงเกี่ยวกับข้อยกเว้น นี่คือตัวอย่าง:
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
destructor อย่างเหมาะสมในทุกกรณี ออบเจ็กต์ที่จัดสรรในสแต็กคือ "คลายออก" เมื่อออกจากขอบเขต (นี่คือขอบเขตของฟังก์ชั่นfunc
) สิ่งนี้กระทำโดยคอมไพเลอร์ที่แทรกการเรียกไปยัง destructors ของตัวแปรอัตโนมัติ (สแต็ค)
ตอนนี้นี่เป็นแนวคิดที่ทรงพลังมากซึ่งนำไปสู่เทคนิคที่เรียกว่าRAIIนั่นคือการได้รับทรัพยากรคือการเริ่มต้นซึ่งช่วยให้เราจัดการทรัพยากรเช่นหน่วยความจำการเชื่อมต่อฐานข้อมูลตัวอธิบายไฟล์แบบเปิดเป็นต้นใน C ++
delete [] pleak;
จะทำได้ก็ต่อเมื่อ x == 0
ทั้งหมดนี้เกี่ยวข้องกับ C ++:
คำจำกัดความ : เมื่อคุณสร้างวัตถุแบบสแตติก (บนสแต็กซึ่งตรงข้ามกับการจัดสรรวัตถุเหล่านั้นในหน่วยความจำฮีป) และดำเนินการเรียกใช้ฟังก์ชันพวกเขาจะ "ซ้อนกัน"
เมื่อขอบเขต (สิ่งใดคั่นด้วย{
และ}
) ถูกออก (โดยใช้return XXX;
ให้ถึงจุดสิ้นสุดของขอบเขตหรือส่งข้อยกเว้น) ทุกอย่างภายในขอบเขตนั้นจะถูกทำลาย (destructors ถูกเรียกใช้สำหรับทุกสิ่ง) กระบวนการทำลายวัตถุในท้องถิ่นและการเรียก destructors นี้เรียกว่าการคลี่คลายสแตก
คุณมีปัญหาต่อไปนี้ที่เกี่ยวข้องกับการคลี่คลายสแตก:
หลีกเลี่ยงการรั่วไหลของหน่วยความจำ (สิ่งใดจัดสรรแบบไดนามิกที่ไม่ได้รับการจัดการโดยวัตถุในท้องถิ่นและทำความสะอาดใน destructor จะรั่วไหล) - ดู RAII ที่อ้างถึงโดย Nikolai และเอกสารสำหรับ boost :: scoped_ptrหรือตัวอย่างของการใช้boost :: mutex ::
ความสอดคล้องของโปรแกรม: สถานะข้อมูลจำเพาะ C ++ ที่คุณไม่ควรทิ้งข้อยกเว้นก่อนที่จะจัดการข้อยกเว้นที่มีอยู่ ซึ่งหมายความว่ากระบวนการคลายสแต็กไม่ควรทิ้งข้อยกเว้น (อาจใช้เฉพาะรหัสที่รับประกันว่าจะไม่โยนเข้าไปใน destructors หรือล้อมรอบทุกอย่างใน destructors ด้วยtry {
และ} catch(...) {}
)
หากผู้ทำลายใด ๆ ขว้างข้อยกเว้นระหว่างการคลี่คลายความรู้สึกของคุณคุณจะพบกับพฤติกรรมที่ไม่ได้กำหนดซึ่งอาจทำให้โปรแกรมของคุณยุติการทำงานโดยไม่คาดคิด (พฤติกรรมที่พบบ่อยที่สุด) หรือเอกภพสิ้นสุด
ในความหมายทั่วไปสแต็ก "ผ่อนคลาย" มีความหมายเหมือนกันมากเมื่อสิ้นสุดการเรียกใช้ฟังก์ชันและการ popping ของสแต็กตามมา
อย่างไรก็ตามโดยเฉพาะอย่างยิ่งในกรณีของ C ++ สแต็คคลี่คลายจะทำอย่างไรกับวิธี C ++ เรียก destructors สำหรับวัตถุที่จัดสรรตั้งแต่เริ่มต้นของบล็อกรหัสใด ๆ วัตถุที่สร้างขึ้นภายในบล็อกจะถูกจัดสรรคืนในลำดับย้อนหลังของการจัดสรร
try
บล็อก วัตถุสแต็คที่จัดสรรในบล็อกใด ๆ ( try
หรือไม่ก็ตาม) อาจมีการคลี่คลายเมื่อบล็อกออก
การคลี่คลายสแตกเป็นส่วนใหญ่เป็นแนวคิด C ++ การจัดการกับวิธีการที่วัตถุที่จัดสรรสแต็คจะถูกทำลายเมื่อออกจากขอบเขตของมัน (ไม่ว่าจะเป็นปกติหรือผ่านข้อยกเว้น)
สมมติว่าคุณมีรหัสส่วนนี้:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
ฉันไม่รู้ว่าคุณอ่านสิ่งนี้หรือไม่ แต่บทความของ Wikipedia ใน call stackมีคำอธิบายที่เหมาะสม
คลี่คลาย:
กลับมาจากฟังก์ชั่นที่เรียกว่าจะปรากฏกรอบด้านบนออกจากสแต็กอาจทิ้งค่าตอบแทน การกระทำโดยทั่วไปของการ popping หนึ่งเฟรมขึ้นไปจากสแต็กเพื่อกลับไปดำเนินการที่อื่นในโปรแกรมเรียกว่าstack unwindingและจะต้องดำเนินการเมื่อใช้โครงสร้างการควบคุมที่ไม่ใช่แบบโลคัลเช่นที่ใช้สำหรับการจัดการข้อยกเว้น ในกรณีนี้เฟรมสแต็กของฟังก์ชันมีหนึ่งรายการขึ้นไปที่ระบุตัวจัดการข้อยกเว้น เมื่อมีข้อผิดพลาดเกิดขึ้นสแต็กจะคลายออกจนกว่าจะพบตัวจัดการที่พร้อมที่จะจัดการ (จับ) ชนิดของข้อยกเว้นที่ส่งออกมา
บางภาษามีโครงสร้างการควบคุมอื่น ๆ ที่ต้องการการคลายตัวทั่วไป Pascal อนุญาตให้ใช้คำสั่ง goto ทั่วโลกเพื่อถ่ายโอนการควบคุมออกจากฟังก์ชั่นที่ซ้อนกันและเป็นฟังก์ชั่นด้านนอกที่เรียกใช้ก่อนหน้านี้ การดำเนินการนี้ต้องการสแต็กที่จะคลายออกลบสแต็กเฟรมได้มากเท่าที่จำเป็นเพื่อกู้คืนบริบทที่เหมาะสมในการถ่ายโอนการควบคุมไปยังคำสั่งเป้าหมายภายในฟังก์ชั่นด้านนอกที่ล้อมรอบ ในทำนองเดียวกัน C มีฟังก์ชัน setjmp และ longjmp ที่ทำหน้าที่เป็น gotos ที่ไม่ใช่แบบโลคัล Common Lisp ช่วยให้สามารถควบคุมสิ่งที่เกิดขึ้นเมื่อกองกำลังคลายโดยใช้ตัวดำเนินการพิเศษป้องกันการคลาย
เมื่อใช้ความต่อเนื่องสแต็กคือ (เชิงตรรกะ) คลายออกแล้วย้อนกลับด้วยสแต็คของความต่อเนื่อง นี่ไม่ใช่วิธีเดียวที่จะใช้การต่อเนื่อง ตัวอย่างเช่นการใช้สแต็คจำนวนมากอย่างชัดเจนการใช้ความต่อเนื่องสามารถเปิดใช้งานสแต็กและกำหนดค่าให้ส่งผ่านได้ ภาษาการเขียนโปรแกรม Scheme อนุญาตให้ดำเนินการ thunks ตามอำเภอใจในจุดที่ระบุบน "คลี่คลาย" หรือ "rewinding" ของกองควบคุมเมื่อมีการเรียกใช้ความต่อเนื่อง
ตรวจสอบ [แก้ไข]
ฉันอ่านโพสต์บล็อกที่ช่วยให้ฉันเข้าใจ
กองคลี่คลายคืออะไร?
ในทุกภาษาที่รองรับฟังก์ชั่นแบบเรียกซ้ำ (เช่นทุกอย่างยกเว้น Fortran 77 และ Brainf * ck) รันไทม์ภาษาจะเก็บสแต็กของฟังก์ชันที่กำลังดำเนินการอยู่ การคลี่คลายสแต็กเป็นวิธีการตรวจสอบและอาจแก้ไขสแต็กนั้น
ทำไมคุณต้องการทำเช่นนั้น?
คำตอบอาจดูเหมือนชัดเจน แต่มีหลายสถานการณ์ที่เกี่ยวข้อง แต่แตกต่างกันอย่างละเอียดสถานการณ์ที่คลี่คลายเป็นประโยชน์หรือจำเป็น:
- เป็นกลไกการควบคุมการไหลแบบรันไทม์ (ข้อยกเว้น C ++, C longjmp (), ฯลฯ )
- ในตัวดีบักเกอร์เพื่อแสดงสแต็กผู้ใช้
- ใน profiler เพื่อนำตัวอย่างของสแต็ก
- จากโปรแกรมเอง (เช่นจากตัวจัดการความผิดพลาดเพื่อแสดงสแต็ก)
สิ่งเหล่านี้มีข้อกำหนดที่แตกต่างกันเล็กน้อย บางส่วนของสิ่งเหล่านี้มีความสำคัญต่อประสิทธิภาพ แต่บางตัวก็ไม่ใช่ บางคนต้องการความสามารถในการสร้างรีจิสเตอร์จากเฟรมด้านนอกบางตัวไม่ได้ แต่เราจะเข้าสู่ทุกสิ่งภายในไม่กี่วินาที
ทุกคนได้พูดคุยเกี่ยวกับการจัดการข้อยกเว้นใน C ++ แต่ฉันคิดว่ามีความหมายอื่นสำหรับการคลายสแต็กและเกี่ยวข้องกับการดีบัก ดีบักเกอร์จะต้องทำการคลี่คลายสแต็กเมื่อใดก็ตามที่ควรจะไปที่เฟรมก่อนหน้าเฟรมปัจจุบัน อย่างไรก็ตามนี่เป็นเสมือนการคลี่คลายเนื่องจากต้องย้อนกลับเมื่อมาถึงเฟรมปัจจุบัน ตัวอย่างสำหรับสิ่งนี้อาจเป็นคำสั่ง up / down / bt ใน gdb
IMO แผนภาพด้านล่างที่ให้ไว้ในบทความนี้อธิบายถึงผลกระทบของการคลี่คลายสแต็คบนเส้นทางของคำสั่งถัดไป (จะถูกดำเนินการเมื่อข้อยกเว้นถูกโยนทิ้งซึ่งไม่ถูกตรวจจับ):
ในรูป:
ในกรณีที่สองเมื่อเกิดข้อยกเว้นสแตกการเรียกฟังก์ชันจะค้นหาตัวจัดการข้อยกเว้นเชิงเส้น การค้นหาจะจบลงที่ฟังก์ชันด้วยตัวจัดการข้อยกเว้นเช่นmain()
กับtry-catch
บล็อกที่ล้อมรอบแต่ไม่ใช่ก่อนที่จะลบรายการทั้งหมดก่อนที่จะออกจากสแต็กการเรียกใช้ฟังก์ชัน
C ++ รันไทม์ทำลายตัวแปรอัตโนมัติทั้งหมดที่สร้างขึ้นระหว่างระหว่างการส่ง & การจับ ในตัวอย่างง่ายๆด้านล่างนี้ f1 () throws และ main () catches ระหว่างวัตถุประเภท B และ A ถูกสร้างขึ้นบนสแต็กตามลำดับนั้น เมื่อ f1 () ขว้าง destructors ของ 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 () เป็นตัวแปรอัตโนมัติที่ผุดแล้วจะถูกทำลาย
หวังว่านี่จะช่วยได้ขอให้มีความสุขในการเข้ารหัส!
เมื่อมีข้อผิดพลาดเกิดขึ้นและการควบคุมส่งผ่านจากบล็อกลองไปยังตัวจัดการ C ++ รันไทม์จะเรียกใช้ destructors สำหรับวัตถุอัตโนมัติทั้งหมดที่สร้างขึ้นตั้งแต่เริ่มต้นบล็อกลอง กระบวนการนี้เรียกว่าการคลี่คลายสแตก วัตถุอัตโนมัติถูกทำลายในลำดับย้อนหลังของการก่อสร้าง (วัตถุอัตโนมัติเป็นวัตถุในท้องถิ่นที่ได้รับการประกาศโดยอัตโนมัติหรือลงทะเบียนหรือไม่ประกาศคงที่หรือภายนอกวัตถุอัตโนมัติ x จะถูกลบเมื่อใดก็ตามที่โปรแกรมออกจากบล็อกที่มีการประกาศ x)
หากมีข้อผิดพลาดเกิดขึ้นระหว่างการสร้างวัตถุที่ประกอบด้วย subobjects หรือองค์ประกอบอาเรย์ destructors จะถูกเรียกใช้เฉพาะสำหรับ subobjects หรืออิลิเมนต์อาร์เรย์ที่สร้างสำเร็จก่อนที่จะทำการยกเว้น destructor สำหรับวัตถุสแตติกท้องถิ่นจะถูกเรียกใช้เฉพาะถ้าวัตถุนั้นสร้างสำเร็จ
ในจาวาสแต็กที่ไม่รู้จักหรือไม่เปิดไม่สำคัญมาก ในเอกสารการจัดการข้อยกเว้นหลายอย่างฉันเห็นแนวคิดนี้ (การคลี่คลายสแต็ก) โดยเฉพาะนักเขียนเหล่านั้นจะจัดการกับข้อยกเว้นใน C หรือ C ++ กับtry catch
บล็อกเรา shouln't ลืม: สแต็คฟรีจากวัตถุทั้งหมดหลังจากบล็อกท้องถิ่น