1. Mở đầu: Stack và Heap dưới góc nhìn C++

Trong cộng đồng C++, các thuật ngữ ngăn xếp (stack) và vùng nhớ heap được sử dụng rất phổ biến khi nói về phân bổ bộ nhớ. Tuy nhiên, xét một cách hình thức và chặt chẽ theo chuẩn ngôn ngữ, C++ không định nghĩa trực tiếp “stack” hay “heap”. Thay vào đó, C++ mô tả bộ nhớ thông qua các khái niệm trừu tượng hơn như storage duration (thời gian tồn tại của đối tượng), storage class (lớp lưu trữ) và free store (không gian cấp phát động). Dù vậy, trong thực tế triển khai trên các hệ điều hành hiện đại, việc ánh xạ các khái niệm này xuống hai vùng nhớ vật lý–logic là stack và heap giúp lập trình viên hiểu rõ hơn cách chương trình vận hành. Bài viết này tiếp tục đào sâu vào ngăn xếp, vùng nhớ đóng vai trò then chốt trong cơ chế phân bổ bộ nhớ tự động của C++.


Như đã đề cập trong bài viết trước, ngăn xếp là một vùng bộ nhớ trong không gian địa chỉ ảo của tiến trình, được sử dụng để lưu trữ các biến cục bộ và các đối số của hàm. Mỗi khi một hàm được gọi, ngăn xếp sẽ mở rộng; và mỗi khi hàm kết thúc, ngăn xếp sẽ thu hẹp lại. Cơ chế này phản ánh trực tiếp luồng thực thi của chương trình. Một đặc điểm quan trọng trong lập trình song song là mỗi luồng có một ngăn xếp riêng biệt. Điều này khiến stack trở thành vùng nhớ có tính “an toàn luồng” (thread-safe) theo bản chất, bởi các luồng không chia sẻ stack với nhau.


2. Các thuộc tính cốt lõi của ngăn xếp

Ngăn xếp có một số đặc tính rất rõ ràng, tạo nên sự khác biệt căn bản so với vùng nhớ heap.

  • Trước hết, ngăn xếp là một khối bộ nhớ liền kề. Do đó, nó không bị phân mảnh theo thời gian, trái ngược với heap – nơi các khối bộ nhớ được cấp phát và giải phóng với kích thước linh hoạt. Tuy nhiên, tính liền kề này cũng đồng nghĩa với việc stack có kích thước tối đa cố định, được xác định khi luồng được tạo.


  • Thứ hai, khi chương trình vượt quá giới hạn kích thước của stack, hiện tượng tràn ngăn xếp (stack overflow) sẽ xảy ra và thường dẫn đến việc chương trình bị kết thúc đột ngột. Đây là một lỗi nghiêm trọng và khó khắc phục nếu không hiểu rõ cơ chế hoạt động của stack.


  • Cuối cùng, cấp phát và giải phóng bộ nhớ trên stack là rất nhanh. Về mặt khái niệm, nó chỉ đơn giản là việc dịch chuyển con trỏ ngăn xếp (stack pointer) lên hoặc xuống một vị trí mới, không cần đến các thuật toán quản lý phức tạp như trên heap.


Sơ đồ minh họa cho thấy cách bộ nhớ ngăn xếp thay đổi trong quá trình gọi hàm.

  • Ban đầu, biến cục bộ x được tạo trong phạm vi của hàm main. Khi main gọi hàm Add, một khung ngăn xếp (stack frame) mới được đẩy vào ngăn xếp.Khung ngăn xếp này chứa đầy đủ thông tin cần thiết để hàm Add hoạt động và sau đó có thể quay trở lại main. Cụ thể, nó bao gồm các tham số của hàm, các biến cục bộ, địa chỉ trả về, con trỏ cơ sở (base pointer) và dữ liệu liên quan đến giá trị trả về. Con trỏ ngăn xếp vì thế được dịch chuyển xuống sâu hơn trong không gian bộ nhớ.


  • Khi hàm Add kết thúc, toàn bộ khung ngăn xếp của nó sẽ bị loại bỏ. Con trỏ ngăn xếp quay trở lại vị trí trước đó, và chương trình tiếp tục thực thi trong phạm vi của main. Toàn bộ quá trình này diễn ra một cách tự động, minh họa rõ ràng cho khái niệm phân bổ bộ nhớ tự động trong C++.


Việc các tham số hàm và biến cục bộ được lưu trữ trên ngăn xếp chính là biểu hiện điển hình của phân bổ bộ nhớ tự động. Thời gian tồn tại của các đối tượng này gắn chặt với phạm vi mà chúng được khai báo. Khi phạm vi kết thúc, đối tượng không còn tồn tại và bộ nhớ được thu hồi ngay lập tức. Khi một luồng được tạo, hệ điều hành sẽ cấp phát cho nó một vùng stack dưới dạng một khối bộ nhớ liền kề. Mỗi lần gọi hàm hoặc khai báo biến cục bộ, con trỏ ngăn xếp sẽ tiến dần về phía đáy của khối này. Nếu quá trình đó vượt quá ranh giới được cấp phát, tràn ngăn xếp sẽ xảy ra – một giới hạn vật lý–logic mà lập trình viên cần đặc biệt lưu ý.


3. Kết luận và định hướng tiếp theo

Ngăn xếp là nền tảng cho cơ chế thực thi hàm và quản lý biến cục bộ trong C++. Tốc độ cao, tính đơn giản và khả năng tự động quản lý khiến stack trở thành lựa chọn tối ưu cho nhiều trường hợp. Tuy nhiên, giới hạn kích thước cố định cũng đặt ra những ràng buộc quan trọng trong thiết kế chương trình. Trong phần tiếp theo, chúng ta sẽ đi sâu hơn vào các thí nghiệm và quan sát cụ thể với biến trên ngăn xếp, từ đó làm rõ hơn mối liên hệ giữa mô hình bộ nhớ, thời gian tồn tại của đối tượng và hành vi thực tế của chương trình C++.