Ngăn xếp ngăn xếp là gì? Tìm kiếm thông qua nhưng không thể tìm thấy câu trả lời khai sáng!
Ngăn xếp ngăn xếp là gì? Tìm kiếm thông qua nhưng không thể tìm thấy câu trả lời khai sáng!
Câu trả lời:
Giải nén ngăn xếp thường được nói đến liên quan đến xử lý ngoại lệ. Đây là một ví dụ:
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;
}
Ở đây bộ nhớ được phân bổ pleak
sẽ bị mất nếu ném ngoại lệ, trong khi bộ nhớ được cấp phát s
sẽ được giải phóng đúng cách bởi hàm std::string
hủy trong mọi trường hợp. Các đối tượng được phân bổ trên ngăn xếp là "không mở" khi phạm vi được thoát (ở đây phạm vi là của hàm func
.) Điều này được thực hiện bằng trình biên dịch chèn các lệnh gọi tới hàm hủy của biến tự động (ngăn xếp).
Bây giờ đây là một khái niệm rất mạnh mẽ dẫn đến kỹ thuật gọi là RAII , đó là Tài nguyên thu thập là khởi tạo , giúp chúng ta quản lý các tài nguyên như bộ nhớ, kết nối cơ sở dữ liệu, mô tả tệp mở, v.v. trong C ++.
Bây giờ cho phép chúng tôi cung cấp đảm bảo an toàn ngoại lệ .
delete [] pleak;
chỉ đạt được nếu x == 0.
Tất cả điều này liên quan đến C ++:
Định nghĩa : Khi bạn tạo các đối tượng tĩnh (trên ngăn xếp trái ngược với việc phân bổ chúng trong bộ nhớ heap) và thực hiện các lệnh gọi hàm, chúng được "xếp chồng lên nhau".
Khi một phạm vi (bất kỳ thứ gì được phân định bởi {
và }
) được thoát ra (bằng cách sử dụng return XXX;
, đến cuối phạm vi hoặc ném ngoại lệ), mọi thứ trong phạm vi đó sẽ bị hủy (hàm hủy được gọi cho mọi thứ). Quá trình phá hủy các đối tượng cục bộ và gọi các hàm hủy được gọi là ngăn chặn stack.
Bạn có các vấn đề sau liên quan đến ngăn xếp ngăn xếp:
tránh rò rỉ bộ nhớ (mọi thứ được phân bổ động mà không được quản lý bởi đối tượng cục bộ và được dọn sạch trong hàm hủy sẽ bị rò rỉ) - xem RAII được đề cập bởi Nikolai và tài liệu về boost :: scoped_ptr hoặc ví dụ này về việc sử dụng boost :: mutex :: scoped_lock .
tính nhất quán của chương trình: thông số kỹ thuật của C ++ nói rằng bạn không bao giờ nên ném ngoại lệ trước khi bất kỳ ngoại lệ hiện có nào được xử lý. Điều này có nghĩa là quá trình giải nén ngăn xếp không bao giờ được ném ngoại lệ (chỉ sử dụng mã được đảm bảo không ném vào các hàm hủy hoặc bao quanh mọi thứ trong các hàm hủy với try {
và } catch(...) {}
).
Nếu bất kỳ kẻ hủy diệt nào ném một ngoại lệ trong ngăn xếp, bạn sẽ kết thúc ở vùng đất của hành vi không xác định có thể khiến chương trình của bạn chấm dứt bất ngờ (hành vi phổ biến nhất) hoặc vũ trụ kết thúc (về mặt lý thuyết là có thể nhưng chưa được quan sát trong thực tế).
Nói một cách tổng quát, một ngăn xếp "bung ra" đồng nghĩa với việc kết thúc một cuộc gọi chức năng và sự xuất hiện tiếp theo của ngăn xếp.
Tuy nhiên, cụ thể là trong trường hợp của C ++, việc giải nén stack phải liên quan đến cách C ++ gọi các hàm hủy cho các đối tượng được phân bổ kể từ khi bắt đầu bất kỳ khối mã nào. Các đối tượng được tạo trong khối được sắp xếp lại theo thứ tự ngược của phân bổ của chúng.
try
các khối. Các đối tượng ngăn xếp được phân bổ trong bất kỳ khối nào (có try
hoặc không) có thể giải phóng khi khối thoát ra.
Giải nén ngăn xếp là một khái niệm chủ yếu là C ++, xử lý cách các đối tượng được phân bổ ngăn xếp bị phá hủy khi phạm vi của nó được thoát ra (thông thường hoặc thông qua một ngoại lệ).
Giả sử bạn có đoạn mã này:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Tôi không biết nếu bạn đã đọc điều này chưa, nhưng bài viết của Wikipedia về ngăn xếp cuộc gọi có một lời giải thích hợp lý.
Không liên quan:
Trở về từ hàm được gọi sẽ bật khung hình trên cùng của ngăn xếp, có thể để lại giá trị trả về. Hành động tổng quát hơn của việc bật một hoặc nhiều khung ra khỏi ngăn xếp để tiếp tục thực thi ở nơi khác trong chương trình được gọi là giải nén ngăn xếp và phải được thực hiện khi sử dụng các cấu trúc điều khiển không cục bộ, như các cấu trúc được sử dụng để xử lý ngoại lệ. Trong trường hợp này, khung ngăn xếp của hàm chứa một hoặc nhiều mục chỉ định xử lý ngoại lệ. Khi một ngoại lệ được ném ra, ngăn xếp sẽ được mở ra cho đến khi tìm thấy một trình xử lý được chuẩn bị để xử lý (bắt) loại ngoại lệ được ném.
Một số ngôn ngữ có cấu trúc điều khiển khác yêu cầu tháo gỡ chung. Pascal cho phép một câu lệnh goto toàn cầu chuyển điều khiển ra khỏi hàm lồng nhau và thành hàm ngoài được gọi trước đó. Thao tác này yêu cầu ngăn xếp không được mở, loại bỏ càng nhiều khung ngăn xếp cần thiết để khôi phục bối cảnh thích hợp để chuyển điều khiển sang câu lệnh đích trong hàm bên ngoài kèm theo. Tương tự, C có các hàm setjmp và longjmp hoạt động như các gotos không cục bộ. Lisp thông thường cho phép kiểm soát những gì xảy ra khi ngăn xếp không được mở bằng cách sử dụng toán tử đặc biệt bảo vệ thư giãn.
Khi áp dụng một phần tiếp theo, ngăn xếp là (một cách hợp lý) mở ra và sau đó lặp lại với ngăn xếp của phần tiếp theo. Đây không phải là cách duy nhất để thực hiện tiếp tục; ví dụ, bằng cách sử dụng nhiều ngăn xếp rõ ràng, ứng dụng tiếp tục có thể chỉ cần kích hoạt ngăn xếp của nó và tạo ra một giá trị được truyền. Ngôn ngữ lập trình Scheme cho phép các thunks tùy ý được thực thi tại các điểm được chỉ định trên "tháo gỡ" hoặc "tua lại" của ngăn xếp điều khiển khi tiếp tục được gọi.
Kiểm tra [sửa]
Tôi đọc một bài viết trên blog giúp tôi hiểu.
Ngăn xếp ngăn xếp là gì?
Trong bất kỳ ngôn ngữ nào hỗ trợ các hàm đệ quy (ví dụ: khá nhiều thứ trừ Fortran 77 và Brainf * ck), thời gian chạy ngôn ngữ sẽ giữ một chồng các chức năng hiện đang thực thi. Ngăn xếp ngăn xếp là một cách kiểm tra và có thể sửa đổi ngăn xếp đó.
Tại sao bạn muốn làm điều đó?
Câu trả lời có vẻ rõ ràng, nhưng có một số tình huống liên quan, nhưng khác biệt tinh tế, trong đó việc tháo gỡ là hữu ích hoặc cần thiết:
- Là một cơ chế điều khiển luồng thời gian chạy (ngoại lệ C ++, C longjmp (), v.v.).
- Trong trình gỡ lỗi, để hiển thị cho người dùng ngăn xếp.
- Trong một hồ sơ, để lấy một mẫu của ngăn xếp.
- Từ chính chương trình (như từ một trình xử lý sự cố để hiển thị ngăn xếp).
Đây có những yêu cầu tinh tế khác nhau. Một số trong số này là quan trọng về hiệu suất, một số thì không. Một số yêu cầu khả năng xây dựng lại các thanh ghi từ khung bên ngoài, một số thì không. Nhưng chúng ta sẽ nhận được tất cả trong một giây.
Bạn có thể tìm thấy bài viết đầy đủ ở đây .
Mọi người đã nói về việc xử lý ngoại lệ trong C ++. Nhưng, tôi nghĩ có một ý nghĩa khác cho việc giải nén stack và điều đó có liên quan đến việc gỡ lỗi. Trình gỡ lỗi phải thực hiện ngăn xếp ngăn xếp bất cứ khi nào cần phải đi đến một khung trước khung hiện tại. Tuy nhiên, đây là loại bỏ ảo khi nó cần tua lại khi quay lại khung hình hiện tại. Ví dụ cho điều này có thể là các lệnh lên / xuống / bt trong gdb.
IMO, sơ đồ được đưa ra dưới đây trong bài viết này giải thích rất hay về tác dụng của việc giải nén ngăn xếp trên tuyến của lệnh tiếp theo (sẽ được thực hiện khi một ngoại lệ được ném ra mà không bị phát hiện):
Trong ảnh:
Trong trường hợp thứ hai, khi xảy ra ngoại lệ, ngăn xếp lệnh gọi hàm được tìm kiếm tuyến tính cho trình xử lý ngoại lệ. Việc tìm kiếm kết thúc tại hàm với trình xử lý ngoại lệ, tức là main()
với try-catch
khối kèm theo , nhưng không phải trước khi xóa tất cả các mục trước khi nó ra khỏi ngăn xếp lệnh gọi hàm.
Thời gian chạy C ++ phá hủy tất cả các biến tự động được tạo giữa giữa ném và bắt. Trong ví dụ đơn giản dưới đây, các cú ném F1 () và các cú bắt main (), ở giữa các đối tượng loại B và A được tạo trên ngăn xếp theo thứ tự đó. Khi ném F1 (), các hàm hủy của B và A được gọi.
#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;
}
Đầu ra của chương trình này sẽ là
B's dtor
A's dtor
Điều này là do callstack của chương trình khi F1 () ném giống như
f1()
f()
main()
Vì vậy, khi F1 () được bật, biến tự động b bị hủy và sau đó khi f () được bật, biến tự động sẽ bị hủy.
Hy vọng điều này sẽ giúp, mã hóa hạnh phúc!
Khi một ngoại lệ được ném và điều khiển chuyển từ khối thử sang trình xử lý, thời gian chạy C ++ gọi hàm hủy cho tất cả các đối tượng tự động được xây dựng kể từ khi bắt đầu khối thử. Quá trình này được gọi là ngăn xếp ngăn xếp. Các đối tượng tự động bị phá hủy theo thứ tự ngược lại của việc xây dựng của họ. (Đối tượng tự động là đối tượng cục bộ đã được khai báo tự động hoặc đăng ký hoặc không được khai báo tĩnh hoặc bên ngoài. Đối tượng tự động x bị xóa bất cứ khi nào chương trình thoát khỏi khối trong đó x được khai báo.)
Nếu một ngoại lệ được ném trong quá trình xây dựng một đối tượng bao gồm các tiểu phần tử hoặc các phần tử mảng, các hàm hủy chỉ được gọi cho các tiểu phần tử hoặc các phần tử mảng được xây dựng thành công trước khi ngoại lệ được ném. Một hàm hủy cho một đối tượng tĩnh cục bộ sẽ chỉ được gọi nếu đối tượng được xây dựng thành công.
Trong ngăn xếp Java vô tình hay vô căn cứ không quan trọng lắm (với trình thu gom rác). Trong nhiều tài liệu xử lý ngoại lệ, tôi đã thấy khái niệm này (stack uninding), đặc biệt là những người viết đề cập đến việc xử lý ngoại lệ trong C hoặc C ++. với try catch
các khối chúng ta sẽ không quên: ngăn xếp miễn phí từ tất cả các đối tượng sau các khối cục bộ .