Giải thích khái niệm khung stack một cách ngắn gọn


200

Dường như tôi có ý tưởng về ngăn xếp cuộc gọi trong thiết kế ngôn ngữ lập trình. Nhưng tôi không thể tìm thấy (có lẽ, tôi chỉ không tìm kiếm đủ mạnh) bất kỳ lời giải thích hợp lý nào về khung stack là gì.

Vì vậy, tôi muốn nhờ ai đó giải thích cho tôi bằng một vài từ.

Câu trả lời:


195

Khung ngăn xếp là khung dữ liệu được đẩy lên ngăn xếp. Trong trường hợp ngăn xếp cuộc gọi, khung ngăn xếp sẽ đại diện cho một lệnh gọi hàm và dữ liệu đối số của nó.

Nếu tôi nhớ chính xác, địa chỉ trả về hàm được đẩy lên ngăn xếp trước, sau đó là đối số và khoảng trắng cho các biến cục bộ. Cùng nhau, họ tạo ra "khung", mặc dù điều này có thể phụ thuộc vào kiến ​​trúc. Bộ xử lý biết có bao nhiêu byte trong mỗi khung và di chuyển con trỏ ngăn xếp tương ứng khi các khung được đẩy và bật ra khỏi ngăn xếp.

BIÊN TẬP:

Có một sự khác biệt lớn giữa ngăn xếp cuộc gọi cấp cao hơn và ngăn xếp cuộc gọi của bộ xử lý.

Khi chúng ta nói về ngăn xếp cuộc gọi của bộ xử lý, chúng ta đang nói về việc làm việc với các địa chỉ và giá trị ở mức byte / từ trong cụm hoặc mã máy. Có "ngăn xếp cuộc gọi" khi nói về các ngôn ngữ cấp cao hơn, nhưng chúng là một công cụ gỡ lỗi / thời gian chạy được quản lý bởi môi trường thời gian chạy để bạn có thể ghi lại những gì đã xảy ra với chương trình của bạn (ở mức cao). Ở cấp độ này, những thứ như số dòng và phương thức và tên lớp thường được biết đến. Vào thời điểm bộ xử lý nhận được mã, nó hoàn toàn không có khái niệm nào về những điều này.


6
"Bộ xử lý biết có bao nhiêu byte trong mỗi khung và di chuyển con trỏ ngăn xếp tương ứng khi các khung được đẩy và bật ra khỏi ngăn xếp." - Tôi nghi ngờ bộ xử lý biết bất cứ điều gì về stack, bởi vì chúng ta thao tác nó thông qua việc phân nhóm (cấp phát), đẩy và bật. Và đây là cách gọi các quy ước giải thích cách chúng ta nên sử dụng ngăn xếp.
Victor Polevoy

78

Nếu bạn hiểu rất rõ về stack thì bạn sẽ hiểu bộ nhớ hoạt động như thế nào trong chương trình và nếu bạn hiểu cách bộ nhớ hoạt động trong chương trình, bạn sẽ hiểu cách lưu trữ chức năng trong chương trình và nếu bạn hiểu cách lưu trữ chức năng trong chương trình, bạn sẽ hiểu chức năng đệ quy hoạt động như thế nào và nếu bạn hiểu chức năng đệ quy hoạt động như thế nào bạn sẽ hiểu cách trình biên dịch hoạt động và nếu bạn hiểu cách trình biên dịch hoạt động, tâm trí của bạn sẽ hoạt động như trình biên dịch và bạn sẽ gỡ lỗi bất kỳ chương trình nào rất dễ dàng

Hãy để tôi giải thích cách stack hoạt động:

Trước tiên, bạn phải biết cách các hàm được biểu diễn trong ngăn xếp:

Heap lưu trữ các giá trị được phân bổ động.
Stack lưu trữ giá trị phân bổ và xóa tự động.

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

Hãy hiểu ví dụ:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

Bây giờ hãy hiểu các phần của chương trình này:

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

Bây giờ hãy xem stack là gì và các phần stack là gì:

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

Phân bổ ngăn xếp:

Hãy nhớ một điều: nếu điều kiện trả về của bất kỳ hàm nào được thỏa mãn, bất kể nó có tải các biến cục bộ hay không, nó sẽ ngay lập tức trở về từ ngăn xếp với khung ngăn xếp của nó. Điều đó có nghĩa là bất cứ khi nào bất kỳ hàm đệ quy nào được thỏa mãn điều kiện cơ sở và chúng ta đặt trả về sau điều kiện cơ sở, điều kiện cơ sở sẽ không chờ để tải các biến cục bộ được đặt trong phần khác của chương trình. Nó sẽ ngay lập tức trả về khung hiện tại từ ngăn xếp theo đó khung tiếp theo hiện đang ở trong bản ghi kích hoạt.

Xem điều này trong thực tế:

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

Giao dịch của khối:

Vì vậy, bây giờ bất cứ khi nào một hàm gặp câu lệnh return, nó sẽ xóa khung hiện tại khỏi ngăn xếp.

Trong khi trả về từ ngăn xếp, các giá trị sẽ được trả lại theo thứ tự ban đầu mà chúng được phân bổ trong ngăn xếp.

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


3
ngăn xếp phát triển xuống và đống lớn lên, bạn có chúng đảo ngược trong sơ đồ của bạn. SƠ ĐỒ ĐÚNG TẠI ĐÂY
Rafael

@Rafael xin lỗi vì sự nhầm lẫn, tôi đã nói về Hướng tăng trưởng, tôi không nói về hướng tăng trưởng. Có sự khác biệt giữa hướng tăng trưởng và hướng tăng trưởng ngăn xếp. Xem tại đây stackoverflow.com/questions/1677415/
Mạnh

2
Rafael nói đúng. Ngoài ra hình ảnh đầu tiên là sai. Thay thế nó bằng một cái gì đó khác (tìm kiếm hình ảnh google cho "heap stack").
Nikos

Vì vậy, nếu tôi hiểu chính xác, trong sơ đồ thứ ba của bạn, có 3 khung ngăn xếp vì hello()đã được gọi đệ quy hello()mà sau đó (một lần nữa) được gọi đệ quy hello()và khung toàn cầu là hàm ban đầu được gọi là đầu tiên hello()?
Andy J

1
Các liên kết dẫn chúng ta đến đâu ?? Vì mối quan tâm nghiêm trọng về bảo mật, các liên kết này nên được gỡ bỏ càng sớm càng tốt.
Shivanshu

45

Một bọc nhanh chóng. Có lẽ ai đó có một lời giải thích tốt hơn.

Một ngăn xếp cuộc gọi bao gồm 1 hoặc nhiều khung ngăn xếp. Mỗi khung ngăn xếp tương ứng với một cuộc gọi đến một chức năng hoặc thủ tục chưa kết thúc bằng trả về.

Để sử dụng khung ngăn xếp, một luồng giữ hai con trỏ, một được gọi là Con trỏ ngăn xếp (SP) và luồng còn lại được gọi là Con trỏ khung (FP). SP luôn trỏ đến "đỉnh" của ngăn xếp và FP luôn trỏ đến "đỉnh" của khung. Ngoài ra, luồng cũng duy trì bộ đếm chương trình (PC) trỏ đến lệnh tiếp theo sẽ được thực thi.

Sau đây được lưu trữ trên ngăn xếp: biến cục bộ và thời gian, tham số thực tế của lệnh hiện tại (thủ tục, hàm, v.v.)

Có các quy ước gọi khác nhau liên quan đến việc làm sạch ngăn xếp.


7
Đừng quên rằng địa chỉ trả về của chương trình con đi vào ngăn xếp.
Tony R

4
Con trỏ khung cũng là Con trỏ cơ sở theo thuật ngữ x86
peterchaula

1
Tôi muốn nhấn mạnh rằng một con trỏ khung chỉ vào đầu của khung stack cho hiện thân thủ tục hiện đang hoạt động.
Máy chủ Khalilov

13

"Một ngăn xếp cuộc gọi bao gồm các khung ngăn xếp ..." -  Wikipedia

Khung stack là một thứ mà bạn đặt trên stack. Chúng là các cấu trúc dữ liệu chứa thông tin về chương trình con cần gọi.


Xin lỗi, tôi không biết làm thế nào tôi bỏ lỡ điều này trên wiki. Cảm ơn. Tôi có hiểu chính xác không, rằng trong các ngôn ngữ động, kích thước của khung không phải là một giá trị không đổi vì các địa phương của hàm không được biết chính xác?
ikostia

Kích thước và tính chất của khung phụ thuộc rất nhiều vào kiến ​​trúc của máy. Trong thực tế, mô hình của một ngăn xếp cuộc gọi là đặc thù kiến ​​trúc. Theo như tôi biết thì nó luôn luôn thay đổi bởi vì các lệnh gọi hàm khác nhau sẽ có lượng dữ liệu đối số khác nhau.
Tony R

Lưu ý rằng kích thước của khung ngăn xếp phải được bộ xử lý biết khi nó được thao tác. Khi điều này xảy ra, kích thước của dữ liệu đã được xác định. Ngôn ngữ động được biên dịch thành mã máy giống như ngôn ngữ tĩnh, nhưng thường được thực hiện ngay lập tức để trình biên dịch có thể duy trì tính năng động và bộ xử lý có thể hoạt động với kích thước khung "đã biết". Đừng nhầm lẫn các ngôn ngữ cấp cao hơn với mã máy / lắp ráp, đó là nơi công cụ này đang thực sự xảy ra.
Tony R

Chà, nhưng ngôn ngữ động cũng có ngăn xếp cuộc gọi của họ, phải không? Ý tôi là, nếu, giả sử, Python muốn thực thi một số thủ tục, dữ liệu về thủ tục này được lưu trữ bên trong cấu trúc của một số trình thông dịch Python, tôi có đúng không? Vì vậy, tôi có nghĩa là ngăn xếp cuộc gọi không chỉ ở mức thấp.
ikostia

Sau khi đọc một chút về bài viết trên wikipedia đó, tôi đứng lại sửa (một chút). Kích thước của khung stack có thể vẫn chưa xác định tại thời điểm biên dịch . Nhưng vào thời điểm bộ xử lý làm việc với stack + frame con trỏ, nó phải biết kích thước là gì. Kích thước có thể thay đổi nhưng bộ xử lý biết kích thước, đó là những gì tôi đã cố gắng nói.
Tony R

5

Các lập trình viên có thể có câu hỏi về khung stack không theo nghĩa rộng (rằng đó là một thực thể singe trong ngăn xếp chỉ phục vụ một lệnh gọi hàm và giữ địa chỉ trả về, đối số và biến cục bộ) nhưng theo nghĩa hẹp - khi thuật ngữ stack framesnày được đề cập trong bối cảnh của các tùy chọn trình biên dịch.

Cho dù tác giả của câu hỏi có ý nghĩa hay không, nhưng khái niệm khung stack từ khía cạnh của các tùy chọn trình biên dịch là một vấn đề rất quan trọng, không được đề cập trong các câu trả lời khác ở đây.

Ví dụ: trình biên dịch Microsoft Visual Studio 2015 C / C ++ có các tùy chọn sau liên quan đến stack frames:

  • / Oy (Bỏ qua con trỏ khung)

GCC có những điều sau đây:

  • -fomit-frame-con trỏ (Không giữ con trỏ khung trong một thanh ghi cho các hàm không cần. Điều này tránh các hướng dẫn để lưu, thiết lập và khôi phục các con trỏ khung; nó cũng làm cho một thanh ghi bổ sung có sẵn trong nhiều chức năng )

Trình biên dịch Intel C ++ có các phần sau:

  • -fomit-frame-con trỏ (Xác định xem EBP có được sử dụng làm thanh ghi mục đích chung trong tối ưu hóa không)

có bí danh sau:

  • / Oy

Delphi có tùy chọn dòng lệnh sau:

  • - $ W + (Tạo khung ngăn xếp)

Theo nghĩa cụ thể đó, từ phối cảnh của trình biên dịch, khung ngăn xếp chỉ là mã nhập và thoát cho thường trình , đẩy một neo vào ngăn xếp - cũng có thể được sử dụng để gỡ lỗi và xử lý ngoại lệ. Các công cụ gỡ lỗi có thể quét dữ liệu ngăn xếp và sử dụng các neo này để sao lưu, trong khi định vị call sitestrong ngăn xếp, tức là để hiển thị tên của các hàm theo thứ tự chúng được gọi là phân cấp. Đối với kiến ​​trúc Intel, nó là push ebp; mov ebp, esphoặc entercho mục nhập và mov esp, ebp; pop ebphoặc leaveđể thoát.

Đó là lý do tại sao rất quan trọng để hiểu cho lập trình viên về khung stack là gì khi nói đến các tùy chọn trình biên dịch - bởi vì trình biên dịch có thể kiểm soát việc có tạo mã này hay không.

Trong một số trường hợp, khung ngăn xếp (mã nhập và thoát cho thường trình) có thể được trình biên dịch bỏ qua và các biến sẽ được truy cập trực tiếp thông qua con trỏ ngăn xếp (SP / ESP / RSP) thay vì con trỏ cơ sở thuận tiện (BP / TRÒ CHƠI / RSP). Điều kiện bỏ qua khung stack, ví dụ:

  • hàm là hàm lá (tức là một thực thể cuối không gọi các hàm khác);
  • không có thử / cuối cùng hoặc thử / ngoại trừ hoặc các cấu trúc tương tự, nghĩa là không có ngoại lệ nào được sử dụng;
  • không có thói quen nào được gọi với các tham số gửi đi trên ngăn xếp;
  • hàm không có tham số;
  • chức năng không có mã lắp ráp nội tuyến;
  • Vân vân...

Bỏ qua các khung ngăn xếp (mã nhập và thoát cho thường trình) có thể làm cho mã nhỏ hơn và nhanh hơn, nhưng nó cũng có thể ảnh hưởng tiêu cực đến khả năng của trình gỡ lỗi để quay lại dữ liệu trong ngăn xếp và hiển thị nó cho lập trình viên. Đây là các tùy chọn trình biên dịch xác định theo điều kiện nào một hàm nên có mã nhập và thoát, ví dụ: (a) luôn luôn, (b) không bao giờ, (c) khi cần (chỉ định các điều kiện).


-1

Khung stack là thông tin đóng gói liên quan đến một cuộc gọi chức năng. Thông tin này thường bao gồm các đối số được truyền cho hàm th, các biến cục bộ và nơi để trả về khi kết thúc. Bản ghi kích hoạt là tên gọi khác của khung stack. Bố cục của khung ngăn xếp được xác định trong ABI bởi nhà sản xuất và mọi trình biên dịch hỗ trợ ISA phải tuân theo tiêu chuẩn này, tuy nhiên sơ đồ bố trí có thể phụ thuộc vào trình biên dịch. Nói chung kích thước khung ngăn xếp không bị giới hạn nhưng có một khái niệm gọi là "vùng đỏ / được bảo vệ" để cho phép các cuộc gọi hệ thống ... vv thực hiện mà không can thiệp vào khung ngăn xếp.

Luôn có SP nhưng trên một số ABI (ví dụ của ARM và PowerPC) là tùy chọn. Các đối số cần được đặt trên ngăn xếp chỉ có thể được xử lý bằng SP. Việc khung stack có được tạo cho một lệnh gọi hàm hay không phụ thuộc vào loại và số lượng đối số, các biến cục bộ và cách các biến cục bộ được truy cập nói chung. Trên hầu hết các ISA, đầu tiên, các thanh ghi được sử dụng và nếu có nhiều đối số hơn các thanh ghi dành riêng để truyền các đối số thì chúng được đặt trên ngăn xếp (Ví dụ x86 ABI có 6 thanh ghi để truyền các đối số nguyên). Do đó, đôi khi, một số chức năng không cần đặt khung ngăn xếp trên ngăn xếp, chỉ cần địa chỉ trả về được đẩy lên ngăn xếp.

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.