Ngăn xếp ngăn xếp là gì?


193

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!


76
Nếu anh ta không biết nó là gì, làm sao bạn có thể mong đợi anh ta biết chúng không giống nhau đối với C và đối với C ++?
dreamlax

@dreamlax: Vậy, khái niệm "stack uninding" khác nhau như thế nào trong C & C ++?
Kẻ hủy diệt

2
@PravasiMeet: C không có xử lý ngoại lệ, do đó, việc gỡ bỏ ngăn xếp rất đơn giản, tuy nhiên, trong C ++, nếu một ngoại lệ bị ném hoặc một chức năng thoát ra, ngăn xếp ngăn xếp liên quan đến việc phá hủy bất kỳ đối tượng C ++ nào có thời gian lưu trữ tự động.
dreamlax

Câu trả lời:


150

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ổ pleaksẽ bị mất nếu ném ngoại lệ, trong khi bộ nhớ được cấp phát ssẽ được giải phóng đúng cách bởi hàm std::stringhủ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ệ .


Điều đó đã thực sự khai sáng! Vì vậy, tôi nhận được điều này: nếu quá trình của tôi bị sập bất ngờ trong khi rời khỏi BẤT K block khối nào tại thời điểm ngăn xếp được bật lên thì có thể xảy ra mã sau mã xử lý ngoại lệ, sẽ không được thực thi và nó có thể gây rò rỉ bộ nhớ, heap tham nhũng, vv
Rajendra Uppal

15
Nếu chương trình "gặp sự cố" (nghĩa là chấm dứt do lỗi), thì bất kỳ rò rỉ bộ nhớ hoặc hỏng heap đều không liên quan do bộ nhớ được giải phóng khi chấm dứt.
Tyler McHenry

1
Chính xác. Cảm ơn. Hôm nay tôi hơi khó đọc.
Nikolai Fetissov

11
@TylerMcHenry: Tiêu chuẩn không đảm bảo rằng tài nguyên hoặc bộ nhớ được giải phóng khi chấm dứt. Tuy nhiên, hầu hết các hệ điều hành đều làm như vậy.
Vịt mướp

3
delete [] pleak;chỉ đạt được nếu x == 0.
Jib

71

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 {}) đượ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:

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

  2. 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 {} 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ế).


2
Trái lại. Mặc dù gotos không nên bị lạm dụng, nhưng chúng gây ra việc giải nén stack trong MSVC (không phải trong GCC, vì vậy đây có thể là một phần mở rộng). setjmp và longjmp làm điều này theo cách đa nền tảng, với phần nào kém linh hoạt hơn.
Patrick Niedzielski

10
Tôi vừa thử nghiệm điều này với gcc và nó gọi chính xác các hàm hủy khi bạn thoát khỏi khối mã. Xem stackoverflow.com/questions/334780/ - - như đã đề cập trong liên kết đó, đây cũng là một phần của tiêu chuẩn.
Damyan

1
đọc Nikolai, jrista và câu trả lời của bạn theo thứ tự này, bây giờ nó có ý nghĩa!
n611x007

@sashoalm Bạn có thực sự nghĩ cần phải chỉnh sửa một bài đăng bảy năm sau không?
David Hoelzer

41

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.


4
Không có gì đặc biệt về trycá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ó tryhoặc không) có thể giải phóng khi khối thoát ra.
Chris Jester-Young

Đã được một thời gian kể từ khi tôi thực hiện nhiều mã hóa C ++. Tôi đã phải đào câu trả lời đó ra khỏi độ sâu rỉ sét. ; P
jrista

đừng lo lắng Mọi người đôi khi có "cái xấu" của họ.
bitc

13

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"

Điều này áp dụng cho bất kỳ khối? Ý tôi là nếu chỉ có {// một số đối tượng địa phương}
Rajendra Uppal

@Rajendra: Có, một khối ẩn danh xác định một khu vực phạm vi, vì vậy nó cũng được tính.
Michael Myers

12

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]


9

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:

  1. Là một cơ chế điều khiển luồng thời gian chạy (ngoại lệ C ++, C longjmp (), v.v.).
  2. Trong trình gỡ lỗi, để hiển thị cho người dùng ngăn xếp.
  3. Trong một hồ sơ, để lấy một mẫu của ngăn xếp.
  4. 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 .


7

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.


5
Hành động gỡ lỗi thường được gọi là "Stack Walking", đơn giản là phân tích cú pháp stack. "Stack Unwinding" ngụ ý không chỉ "Stack Walking" mà còn gọi các kẻ hủy diệt của các đối tượng tồn tại trên stack.
Adisak

@Adisak Tôi không biết nó còn được gọi là "stack walk". Tôi đã luôn luôn nhìn thấy "stack uninding" trong ngữ cảnh của tất cả các bài viết về trình gỡ lỗi và thậm chí bên trong mã gdb. Tôi cảm thấy "giải nén ngăn xếp" thích hợp hơn vì nó không chỉ là xem trộm thông tin ngăn xếp cho mọi chức năng, mà còn liên quan đến việc giải phóng thông tin khung (cf CFI trong lùn). Điều này được xử lý theo thứ tự từng chức năng.
bbv

Tôi đoán "stack walk" được Windows làm cho nổi tiếng hơn. Ngoài ra, tôi đã tìm thấy dưới dạng ví dụ code.google.com/p/google-breakpad/wiki/StackWalking ngoài chính tài liệu của người lùn sử dụng thuật ngữ gỡ rối vài lần. Mặc dù đồng ý, nó là ảo uninding. Hơn nữa, câu hỏi dường như đang hỏi về mọi ý nghĩa có thể "ngăn xếp ngăn xếp" có thể gợi ý.
bbv

7

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):

nhập mô tả hình ảnh ở đây

Trong ảnh:

  • Top một là thực hiện cuộc gọi bình thường (không có ngoại lệ ném).
  • Dưới cùng một khi một ngoại lệ được ném.

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-catchkhố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.


Sơ đồ là tốt nhưng giải thích là hơi khó hiểu viz. ... với khối chặn bắt kèm theo, nhưng không xóa tất cả các mục trước khi nó ra khỏi ngăn xếp cuộc gọi chức năng ...
Atul

3

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!


2

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.


Bạn nên cung cấp một liên kết đến bài viết gốc nơi bạn đã sao chép câu trả lời này từ: Cơ sở tri thức IBM - Ngăn
chặn không ràng buộc

0

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 catchcá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ộ .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.