Ngăn xếp cho phép chúng tôi bỏ qua các giới hạn áp đặt bởi số lượng đăng ký hữu hạn.
Hãy tưởng tượng có chính xác 26 "thanh ghi az" toàn cầu (hoặc thậm chí chỉ có các thanh ghi có kích thước 7 byte của chip 8080) Và mọi chức năng bạn viết trong ứng dụng này đều chia sẻ danh sách phẳng này.
Một khởi đầu ngây thơ sẽ là phân bổ một vài thanh ghi đầu tiên cho hàm đầu tiên và biết rằng nó chỉ mất 3, bắt đầu bằng "d" cho hàm thứ hai ... Bạn hết nhanh chóng.
Thay vào đó, nếu bạn có một băng ẩn dụ, như máy turing, bạn có thể có mỗi chức năng bắt đầu "gọi một chức năng khác" bằng cách lưu tất cả các biến mà nó sử dụng và chuyển tiếp () băng, và sau đó chức năng callee có thể trộn lẫn với nhiều đăng ký như nó muốn. Khi callee kết thúc, nó sẽ trả lại quyền điều khiển cho hàm cha, người biết nơi để ngắt đầu ra của callee khi cần, sau đó phát băng ngược lại để khôi phục trạng thái của nó.
Khung cuộc gọi cơ bản của bạn chỉ là như vậy, và được tạo và loại bỏ bởi các chuỗi mã máy được tiêu chuẩn hóa mà trình biên dịch đưa vào xung quanh các chuyển đổi từ chức năng này sang chức năng khác. (Đã lâu rồi tôi mới phải nhớ các khung ngăn xếp C của mình, nhưng bạn có thể đọc các cách khác nhau về nhiệm vụ của những người bỏ những gì tại X86_calling_conventions .)
(đệ quy là tuyệt vời, nhưng nếu bạn đã từng phải sắp xếp các thanh ghi mà không có ngăn xếp, thì bạn thực sự đánh giá cao các ngăn xếp.)
Tôi cho rằng dung lượng ổ cứng và RAM tăng cần thiết để lưu trữ chương trình và hỗ trợ quá trình biên dịch của nó (tương ứng) là lý do tại sao chúng tôi sử dụng ngăn xếp cuộc gọi. Đúng không?
Mặc dù chúng ta có thể nội tuyến nhiều hơn trong những ngày này, ("tốc độ nhanh hơn" luôn luôn tốt; "ít kb lắp ráp hơn" có nghĩa là rất ít trong thế giới của các luồng video) Hạn chế chính là ở khả năng của trình biên dịch trong việc làm phẳng các loại mẫu mã nhất định.
Ví dụ: các đối tượng đa hình - nếu bạn không biết một và chỉ một loại đối tượng bạn sẽ được trao, bạn không thể làm phẳng; bạn phải xem vtable các tính năng của đối tượng và gọi qua con trỏ đó ... tầm thường phải làm trong thời gian chạy, không thể nội tuyến trong thời gian biên dịch.
Một chuỗi công cụ hiện đại có thể vui vẻ nội tuyến một hàm được định nghĩa đa hình khi nó đã làm phẳng đủ số người gọi để biết chính xác hương vị của obj là gì:
class Base {
public: void act() = 0;
};
class Child1: public Base {
public: void act() {};
};
void ActOn(Base* something) {
something->act();
}
void InlineMe() {
Child1 thingamabob;
ActOn(&thingamabob);
}
ở trên, trình biên dịch có thể chọn giữ đúng nội tuyến, từ InlineMe cho đến bất kỳ hành động bên trong nào (), cũng không cần phải chạm vào bất kỳ vtables nào khi chạy.
Nhưng bất kỳ sự không chắc chắn trong hương vị của đối tượng sẽ để lại nó như là một cuộc gọi đến một chức năng riêng biệt, ngay cả khi một số lời mời khác của cùng chức năng được đưa vào .