it-swarm-vi.tech

Ngăn xếp và đống là gì và ở đâu?

Sách ngôn ngữ lập trình giải thích rằng các loại giá trị được tạo trên stack và các loại tham chiếu được tạo trên heap , mà không giải thích hai thứ này là gì. Tôi chưa đọc một lời giải thích rõ ràng về điều này. Tôi hiểu a stack là gì. Nhưng, 

  • chúng ở đâu và ở đâu (vật lý trong bộ nhớ của máy tính thật)?
  • Ở mức độ nào chúng được điều khiển bởi hệ điều hành hoặc thời gian chạy ngôn ngữ?
  • Phạm vi của họ là gì?
  • Điều gì quyết định kích thước của mỗi người trong số họ?
  • Điều gì làm cho một người nhanh hơn? 
7473
mattshane

Ngăn xếp là bộ nhớ được đặt sang một bên làm không gian đầu cho một luồng thực thi. Khi một hàm được gọi, một khối được dành riêng trên đỉnh của ngăn xếp cho các biến cục bộ và một số dữ liệu sổ sách kế toán. Khi hàm đó trả về, khối sẽ không được sử dụng và có thể được sử dụng vào lần tiếp theo khi hàm được gọi. Ngăn xếp luôn được bảo lưu theo thứ tự LIFO (cuối cùng ở lần ra trước); khối dành riêng gần đây nhất luôn là khối tiếp theo được giải phóng. Điều này làm cho nó thực sự đơn giản để theo dõi ngăn xếp; giải phóng một khối khỏi ngăn xếp không gì khác hơn là điều chỉnh một con trỏ.

Heap là bộ nhớ dành cho phân bổ động. Không giống như ngăn xếp, không có mô hình bắt buộc nào đối với việc phân bổ và phân bổ các khối từ đống; bạn có thể phân bổ một khối bất cứ lúc nào và giải phóng nó bất cứ lúc nào. Điều này làm cho nó phức tạp hơn nhiều để theo dõi phần nào của heap được phân bổ hoặc miễn phí tại bất kỳ thời điểm nào; có nhiều phân bổ heap tùy chỉnh có sẵn để điều chỉnh hiệu năng của heap cho các kiểu sử dụng khác nhau.

Mỗi luồng có một ngăn xếp, trong khi thông thường chỉ có một đống cho ứng dụng (mặc dù không có nhiều heap cho các loại phân bổ khác nhau).

Để trả lời câu hỏi của bạn trực tiếp: 

Chúng được điều khiển bởi hệ điều hành hoặc thời gian chạy ngôn ngữ ở mức độ nào?

HĐH phân bổ ngăn xếp cho từng luồng cấp hệ thống khi luồng được tạo. Thông thường, HĐH được gọi bởi bộ thực thi ngôn ngữ để phân bổ heap cho ứng dụng.

Phạm vi của họ là gì?

Ngăn xếp được gắn vào một luồng, vì vậy khi luồng ra khỏi ngăn xếp được lấy lại. Heap thường được phân bổ khi khởi động ứng dụng theo thời gian chạy và được lấy lại khi ứng dụng (quy trình kỹ thuật) thoát.

Điều gì quyết định kích thước của mỗi người trong số họ? 

Kích thước của ngăn xếp được đặt khi một luồng được tạo. Kích thước của heap được đặt khi khởi động ứng dụng, nhưng có thể tăng lên khi cần dung lượng (bộ cấp phát yêu cầu thêm bộ nhớ từ hệ điều hành).

Điều gì làm cho một người nhanh hơn?

Ngăn xếp nhanh hơn bởi vì mẫu truy cập làm cho việc phân bổ và phân bổ bộ nhớ từ nó trở nên tầm thường (một con trỏ/số nguyên chỉ đơn giản là tăng hoặc giảm), trong khi heap có sổ sách kế toán phức tạp hơn nhiều liên quan đến phân bổ hoặc phân bổ. Ngoài ra, mỗi byte trong ngăn xếp có xu hướng được sử dụng lại rất thường xuyên, điều đó có nghĩa là nó có xu hướng được ánh xạ tới bộ đệm của bộ xử lý, làm cho nó rất nhanh. Một hiệu suất khác cho heap là heap, phần lớn là tài nguyên toàn cầu, thường phải an toàn đa luồng, tức là mỗi phân bổ và phân bổ cần phải - thường được đồng bộ hóa với "tất cả" các truy cập heap khác trong chương trình.

Một minh chứng rõ ràng:
Nguồn hình ảnh: vikashazrati.wordpress.com

5529
Jeff Hill

Cây rơm:

  • Được lưu trữ trong máy tính RAM giống như heap.
  • Các biến được tạo trên ngăn xếp sẽ đi ra khỏi phạm vi và được tự động giải quyết.
  • Nhanh hơn nhiều để phân bổ so với các biến trên heap.
  • Được thực hiện với một cấu trúc dữ liệu ngăn xếp thực tế.
  • Lưu trữ dữ liệu cục bộ, địa chỉ trả về, được sử dụng để truyền tham số.
  • Có thể có tràn ngăn xếp khi sử dụng quá nhiều ngăn xếp (chủ yếu là từ đệ quy vô hạn hoặc quá sâu, phân bổ rất lớn).
  • Dữ liệu được tạo trên ngăn xếp có thể được sử dụng mà không cần con trỏ.
  • Bạn sẽ sử dụng ngăn xếp nếu bạn biết chính xác lượng dữ liệu bạn cần phân bổ trước khi biên dịch thời gian và nó không quá lớn.
  • Thường có kích thước tối đa đã được xác định khi chương trình của bạn bắt đầu.

Heap:

  • Được lưu trữ trong máy tính RAM giống như ngăn xếp.
  • Trong C++, các biến trên heap phải được hủy bằng tay và không bao giờ nằm ​​ngoài phạm vi. Dữ liệu được giải phóng với delete, delete[] hoặc free.
  • Chậm hơn để phân bổ so với các biến trên ngăn xếp.
  • Được sử dụng theo yêu cầu để phân bổ một khối dữ liệu để sử dụng bởi chương trình.
  • Có thể có sự phân mảnh khi có rất nhiều phân bổ và thỏa thuận.
  • Trong C++ hoặc C, dữ liệu được tạo trên heap sẽ được trỏ đến bởi các con trỏ và được phân bổ với new hoặc malloc tương ứng.
  • Có thể có lỗi phân bổ nếu quá lớn bộ đệm được yêu cầu phân bổ.
  • Bạn sẽ sử dụng heap nếu bạn không biết chính xác mình sẽ cần bao nhiêu dữ liệu trong thời gian chạy hoặc nếu bạn cần phân bổ nhiều dữ liệu.
  • Chịu trách nhiệm về rò rỉ bộ nhớ.

Thí dụ:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;
2199
Brian R. Bondy

Điểm quan trọng nhất là heap và stack là các thuật ngữ chung cho các cách mà bộ nhớ có thể được phân bổ. Chúng có thể được thực hiện theo nhiều cách khác nhau và các điều khoản áp dụng cho các khái niệm cơ bản.

  • Trong một chồng các vật phẩm, các vật phẩm xếp chồng lên nhau theo thứ tự chúng được đặt ở đó và bạn chỉ có thể loại bỏ vật phẩm trên cùng (mà không lật đổ toàn bộ vật phẩm).

    Stack like a stack of papers

    Sự đơn giản của ngăn xếp là bạn không cần duy trì một bảng chứa bản ghi của từng phần của bộ nhớ được phân bổ; thông tin trạng thái duy nhất bạn cần là một con trỏ duy nhất đến cuối ngăn xếp. Để phân bổ và phân bổ lại, bạn chỉ cần tăng và giảm con trỏ đơn đó. Lưu ý: đôi khi một ngăn xếp có thể được thực hiện để bắt đầu ở đầu một phần của bộ nhớ và kéo dài xuống dưới thay vì tăng lên.

  • Trong một đống, không có thứ tự cụ thể nào cho cách đặt vật phẩm. Bạn có thể tiếp cận và xóa các mục theo bất kỳ thứ tự nào vì không có mục 'trên cùng' rõ ràng.

    Heap like a heap of licorice allsorts

    Phân bổ heap yêu cầu duy trì một bản ghi đầy đủ về bộ nhớ được phân bổ và những gì không, cũng như một số bảo trì trên cao để giảm phân mảnh, tìm các phân đoạn bộ nhớ liền kề đủ lớn để phù hợp với kích thước được yêu cầu, v.v. Bộ nhớ có thể được giải phóng bất cứ lúc nào để lại không gian trống. Đôi khi, bộ cấp phát bộ nhớ sẽ thực hiện các nhiệm vụ bảo trì như chống phân mảnh bộ nhớ bằng cách di chuyển bộ nhớ được phân bổ xung quanh hoặc thu gom rác - xác định trong thời gian chạy khi bộ nhớ không còn trong phạm vi và giải phóng nó. 

Những hình ảnh này sẽ làm rất tốt khi mô tả hai cách phân bổ và giải phóng bộ nhớ trong một chồng và một đống. Yum!

  • Ở mức độ nào chúng được điều khiển bởi hệ điều hành hoặc thời gian chạy ngôn ngữ?

    Như đã đề cập, heap và stack là các thuật ngữ chung và có thể được thực hiện theo nhiều cách. Các chương trình máy tính thường có một ngăn xếp được gọi là ngăn xếp cuộc gọi lưu trữ thông tin liên quan đến chức năng hiện tại, chẳng hạn như một con trỏ tới bất kỳ chức năng nào mà nó được gọi từ và bất kỳ biến cục bộ nào. Bởi vì các hàm gọi các hàm khác và sau đó trả về, ngăn xếp phát triển và co lại để giữ thông tin từ các hàm tiếp tục xuống ngăn xếp cuộc gọi. Một chương trình không thực sự có kiểm soát thời gian chạy trên nó; nó được xác định bởi ngôn ngữ lập trình, hệ điều hành và thậm chí cả kiến ​​trúc hệ thống.

    Heap là một thuật ngữ chung được sử dụng cho bất kỳ bộ nhớ nào được phân bổ động và ngẫu nhiên; tức là ra khỏi trật tự Bộ nhớ thường được phân bổ bởi HĐH, với ứng dụng gọi các hàm API để thực hiện phân bổ này. Có một chút chi phí cần thiết trong việc quản lý bộ nhớ được phân bổ động, thường được HĐH xử lý.

  • Phạm vi của họ là gì?

    Ngăn xếp cuộc gọi là một khái niệm cấp thấp đến mức nó không liên quan đến 'phạm vi' theo nghĩa lập trình. Nếu bạn tháo rời một số mã, bạn sẽ thấy các tham chiếu kiểu con trỏ tương đối đến các phần của ngăn xếp, nhưng khi có liên quan đến ngôn ngữ cấp cao hơn, ngôn ngữ sẽ áp đặt các quy tắc phạm vi riêng. Tuy nhiên, một khía cạnh quan trọng của ngăn xếp là một khi hàm trả về, mọi thứ cục bộ của hàm đó sẽ được giải phóng ngay lập tức khỏi ngăn xếp. Điều đó hoạt động theo cách bạn mong đợi nó hoạt động dựa trên cách ngôn ngữ lập trình của bạn hoạt động. Trong một đống, nó cũng khó xác định. Phạm vi là bất cứ điều gì được HĐH phơi bày, nhưng ngôn ngữ lập trình của bạn có thể bổ sung các quy tắc của nó về "phạm vi" trong ứng dụng của bạn. Kiến trúc bộ xử lý và HĐH sử dụng địa chỉ ảo, bộ xử lý dịch thành địa chỉ vật lý và có lỗi trang, v.v. Họ theo dõi những trang nào thuộc về ứng dụng nào. Tuy nhiên, bạn không bao giờ thực sự cần phải lo lắng về điều này, vì bạn chỉ sử dụng bất kỳ phương pháp nào mà ngôn ngữ lập trình của bạn sử dụng để phân bổ và giải phóng bộ nhớ, và kiểm tra lỗi (nếu việc phân bổ/giải phóng không thành công vì bất kỳ lý do nào).

  • Điều gì quyết định kích thước của mỗi người trong số họ?

    Một lần nữa, nó phụ thuộc vào ngôn ngữ, trình biên dịch, hệ điều hành và kiến ​​trúc. Một ngăn xếp thường được phân bổ trước, bởi vì theo định nghĩa, nó phải là bộ nhớ liền kề (nhiều hơn về điều đó trong đoạn cuối). Trình biên dịch ngôn ngữ hoặc HĐH xác định kích thước của nó. Bạn không lưu trữ khối dữ liệu khổng lồ trên ngăn xếp, vì vậy nó sẽ đủ lớn để không bao giờ được sử dụng đầy đủ, ngoại trừ trong trường hợp đệ quy vô tận không mong muốn (do đó, "tràn ngăn xếp") hoặc các quyết định lập trình bất thường khác.

    Một đống là một thuật ngữ chung cho bất cứ điều gì có thể được phân bổ động. Tùy thuộc vào cách bạn nhìn vào nó, nó liên tục thay đổi kích thước. Trong các bộ xử lý và hệ điều hành hiện đại, cách thức hoạt động chính xác của nó rất trừu tượng, vì vậy bạn thường không cần phải lo lắng nhiều về cách thức hoạt động sâu, ngoại trừ (trong các ngôn ngữ cho phép bạn) bạn không được sử dụng bộ nhớ bạn chưa được phân bổ hoặc bộ nhớ mà bạn đã giải phóng.Điều gì làm cho một người nhanh hơn?.

  • What makes one faster?

    The stack is faster because all free memory is always contiguous. No list needs to be maintained of all the segments of free memory, just a single pointer to the current top of the stack. Compilers usually store this pointer in a special, fast register for this purpose. What's more, subsequent operations on a stack are usually concentrated within very nearby areas of memory, which at a very low level is good for optimization by the processor on-die caches.

1305
thomasrutter

(Tôi đã chuyển câu trả lời này từ một câu hỏi khác ít nhiều là một bản sao của câu hỏi này.)

Câu trả lời cho câu hỏi của bạn là triển khai cụ thể và có thể khác nhau giữa các trình biên dịch và kiến ​​trúc bộ xử lý. Tuy nhiên, đây là một lời giải thích đơn giản.

  • Cả stack và heap đều là các vùng nhớ được phân bổ từ hệ điều hành bên dưới (thường là bộ nhớ ảo được ánh xạ tới bộ nhớ vật lý theo yêu cầu).
  • Trong một môi trường đa luồng, mỗi luồng sẽ có ngăn xếp hoàn toàn độc lập của riêng mình nhưng chúng sẽ chia sẻ heap. Truy cập đồng thời phải được kiểm soát trên heap và không thể có trên stack.

Đống

  • Heap chứa một danh sách liên kết của các khối được sử dụng và miễn phí. Phân bổ mới trên heap (bởi new hoặc malloc) được thỏa mãn bằng cách tạo một khối phù hợp từ một trong các khối miễn phí. Điều này đòi hỏi phải cập nhật danh sách các khối trên heap. Điều này thông tin meta về các khối trên heap cũng được lưu trữ trên heap thường trong một khu vực nhỏ ngay trước mỗi khối.
  • Khi heap phát triển, các khối mới thường được phân bổ từ các địa chỉ thấp hơn đến các địa chỉ cao hơn. Do đó, bạn có thể nghĩ heap là một heap của các khối bộ nhớ tăng kích thước khi bộ nhớ được phân bổ. Nếu heap quá nhỏ để phân bổ, kích thước thường có thể được tăng lên bằng cách lấy thêm bộ nhớ từ hệ điều hành bên dưới.
  • Phân bổ và phân bổ nhiều khối nhỏ có thể để lại đống trong trạng thái có rất nhiều khối tự do nhỏ xen kẽ giữa các khối được sử dụng. Yêu cầu phân bổ một khối lớn có thể thất bại vì không có khối miễn phí nào đủ lớn để đáp ứng yêu cầu phân bổ mặc dù kích thước kết hợp của các khối miễn phí có thể đủ lớn. Điều này được gọi là phân mảnh heap.
  • Khi một khối được sử dụng liền kề với một khối miễn phí được giải phóng, khối miễn phí mới có thể được hợp nhất với khối tự do liền kề để tạo ra một khối tự do lớn hơn có hiệu quả làm giảm sự phân mảnh của đống.

The heap

Chồng

  • Ngăn xếp thường hoạt động song song với một thanh ghi đặc biệt trên CPU có tên con trỏ ngăn xếp. Ban đầu con trỏ ngăn xếp chỉ vào đỉnh của ngăn xếp (địa chỉ cao nhất trên ngăn xếp).
  • CPU có các hướng dẫn đặc biệt cho đẩy giá trị lên ngăn xếp và bật chúng trở lại từ ngăn xếp. Mỗi Đẩy lưu giá trị tại vị trí hiện tại của con trỏ ngăn xếp và giảm con trỏ ngăn xếp. A pop lấy giá trị được trỏ bởi con trỏ ngăn xếp và sau đó tăng con trỏ ngăn xếp (không bị nhầm lẫn bởi thực tế là thêm một giá trị vào ngăn xếp giảm con trỏ ngăn xếp và loại bỏ một giá trị tăng nó. Hãy nhớ rằng ngăn xếp phát triển xuống dưới cùng). Các giá trị được lưu trữ và truy xuất là các giá trị của các thanh ghi CPU.
  • Khi một hàm được gọi, CPU sử dụng các lệnh đặc biệt Đẩy dòng con trỏ lệnh, tức là địa chỉ của mã thực thi trên ngăn xếp. CPU sau đó nhảy đến hàm bằng cách đặt con trỏ lệnh tới địa chỉ của hàm được gọi. Sau đó, khi hàm trả về, con trỏ lệnh cũ được bật ra từ ngăn xếp và thực thi lại tiếp tục tại mã ngay sau khi gọi hàm.
  • Khi một hàm được nhập, con trỏ ngăn xếp bị giảm để phân bổ nhiều không gian hơn trên ngăn xếp cho các biến cục bộ (tự động). Nếu hàm có một biến 32 bit cục bộ, bốn byte được đặt sang một bên trên ngăn xếp. Khi hàm trả về, con trỏ ngăn xếp được di chuyển trở lại để giải phóng vùng được phân bổ.
  • Nếu một hàm có các tham số, chúng được đẩy lên ngăn xếp trước khi gọi hàm. Mã trong hàm sau đó có thể điều hướng ngăn xếp từ con trỏ ngăn xếp hiện tại để xác định các giá trị này.
  • Chức năng lồng nhau gọi công việc như một nét duyên dáng. Mỗi cuộc gọi mới sẽ phân bổ các tham số chức năng, địa chỉ trả về và không gian cho các biến cục bộ và bản ghi kích hoạt có thể được xếp chồng lên nhau cho các cuộc gọi lồng nhau và sẽ thư giãn theo cách chính xác khi các hàm trở lại.
  • Vì ngăn xếp là một khối bộ nhớ hạn chế, bạn có thể gây ra tràn ngăn xếp bằng cách gọi quá nhiều hàm lồng nhau và/hoặc phân bổ quá nhiều không gian cho các biến cục bộ. Thông thường vùng nhớ được sử dụng cho ngăn xếp được thiết lập theo cách viết dưới đáy (địa chỉ thấp nhất) của ngăn xếp sẽ kích hoạt bẫy hoặc ngoại lệ trong CPU. Điều kiện đặc biệt này sau đó có thể được bắt gặp bởi bộ thực thi và chuyển đổi thành một loại ngoại lệ ngăn xếp chồng.

The stack

Một chức năng có thể được phân bổ trên heap thay vì một ngăn xếp?

Không, các bản ghi kích hoạt cho các hàm (tức là biến cục bộ hoặc biến tự động) được phân bổ trên ngăn xếp không chỉ được sử dụng để lưu trữ các biến này mà còn để theo dõi các lệnh gọi hàm lồng nhau.

Làm thế nào heap được quản lý thực sự là tùy thuộc vào môi trường thời gian chạy. C sử dụng malloc và C++ sử dụng new, nhưng nhiều ngôn ngữ khác có bộ sưu tập rác.

Tuy nhiên, ngăn xếp là một tính năng cấp thấp hơn gắn chặt với kiến ​​trúc bộ xử lý. Phát triển heap khi không có đủ không gian không quá khó vì nó có thể được thực hiện trong lệnh gọi thư viện xử lý heap. Tuy nhiên, việc phát triển stack thường là không thể vì tràn stack chỉ được phát hiện khi quá muộn; và tắt luồng thực thi là lựa chọn khả thi duy nhất.

702
Martin Liversage

Trong mã C # sau

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

Đây là cách quản lý bộ nhớ

Picture of variables on the stack

Local Variables chỉ cần kéo dài chừng nào lệnh gọi hàm đi trong ngăn xếp. Heap được sử dụng cho các biến có thời gian tồn tại mà chúng ta không thực sự biết trước nhưng chúng ta hy vọng chúng sẽ tồn tại trong một thời gian. Trong hầu hết các ngôn ngữ, điều quan trọng là chúng ta biết tại thời điểm biên dịch một biến lớn đến mức nào nếu chúng ta muốn lưu trữ nó trên ngăn xếp. 

Các đối tượng (có kích thước khác nhau khi chúng tôi cập nhật chúng) đi vào đống vì chúng tôi không biết tại thời điểm tạo chúng sẽ kéo dài bao lâu. Trong nhiều ngôn ngữ, heap là rác được thu thập để tìm các đối tượng (chẳng hạn như đối tượng cls1) không còn có bất kỳ tham chiếu nào. 

Trong Java, hầu hết các đối tượng đi trực tiếp vào heap. Trong các ngôn ngữ như C/C++, các cấu trúc và lớp thường có thể vẫn ở trên ngăn xếp khi bạn không xử lý các con trỏ.

Xem thêm thông tin tại đây:

Sự khác biệt giữa cấp phát bộ nhớ stack và heap «timmurphy.org

và đây: 

Tạo đối tượng trên Stack và Heap

Bài viết này là nguồn của hình ảnh trên: Sáu khái niệm .NET quan trọng: Stack, heap, các loại giá trị, các loại tham chiếu, quyền anh và unboxing - CodeProject

nhưng lưu ý rằng nó có thể chứa một số điểm không chính xác. 

379
Snowcrash

Ngăn xếp Khi bạn gọi một hàm, các đối số cho hàm đó cộng với một số chi phí khác được đặt trên ngăn xếp. Một số thông tin (chẳng hạn như nơi trở về) cũng được lưu trữ ở đó . Khi bạn khai báo một biến trong hàm của mình, biến đó cũng được phân bổ trên ngăn xếp. 

Xử lý ngăn xếp khá đơn giản vì bạn luôn luôn phân bổ theo thứ tự ngược lại trong đó bạn phân bổ. Công cụ ngăn xếp được thêm vào khi bạn nhập chức năng, dữ liệu tương ứng sẽ bị xóa khi bạn thoát chúng. Điều này có nghĩa là bạn có xu hướng ở trong một vùng nhỏ của ngăn xếp trừ khi bạn gọi nhiều hàm gọi nhiều hàm khác (hoặc tạo một giải pháp đệ quy).

Heap Heap là tên chung cho nơi bạn đặt dữ liệu mà bạn tạo khi đang di chuyển. Nếu bạn không biết có bao nhiêu tàu vũ trụ mà chương trình của bạn sẽ tạo, bạn có thể sử dụng toán tử mới (hoặc malloc hoặc tương đương) để tạo mỗi tàu vũ trụ. Việc phân bổ này sẽ diễn ra trong một thời gian, vì vậy có khả năng chúng tôi sẽ giải phóng mọi thứ theo một thứ tự khác so với chúng tôi đã tạo ra chúng. 

Do đó, heap phức tạp hơn nhiều, vì cuối cùng có những vùng bộ nhớ không được sử dụng xen kẽ với các khối - bộ nhớ bị phân mảnh. Tìm bộ nhớ trống của kích thước bạn cần là một vấn đề khó khăn. Đây là lý do tại sao nên tránh đống (mặc dù nó vẫn thường được sử dụng).

Thực hiện Việc thực hiện cả stack và heap thường nằm trong thời gian chạy/HĐH. Thông thường các trò chơi và các ứng dụng quan trọng khác có hiệu năng tạo ra các giải pháp bộ nhớ của riêng chúng, lấy một lượng lớn bộ nhớ từ đống và sau đó xử lý nội bộ để tránh phụ thuộc vào hệ điều hành cho bộ nhớ. 

Điều này chỉ thực tế nếu việc sử dụng bộ nhớ của bạn khá khác so với tiêu chuẩn - tức là đối với các trò chơi mà bạn tải một cấp độ trong một hoạt động lớn và có thể phá hủy toàn bộ hoạt động trong một hoạt động lớn khác.

Vị trí vật lý trong bộ nhớ Điều này ít liên quan hơn bạn nghĩ vì một công nghệ có tên Bộ nhớ ảo khiến chương trình của bạn nghĩ rằng bạn có quyền truy cập vào một địa chỉ nhất định nơi dữ liệu vật lý ở một nơi khác ( thậm chí trên đĩa cứng!). Các địa chỉ bạn nhận được cho ngăn xếp theo thứ tự tăng dần khi cây cuộc gọi của bạn sâu hơn. Các địa chỉ cho heap là không thể dự đoán được (nghĩa là cụ thể) và thực sự không quan trọng.

194
Tom Leys

Để làm rõ, câu trả lời này có thông tin không chính xác ( thomas đã sửa câu trả lời của anh ấy sau khi bình luận, tuyệt :)). Các câu trả lời khác chỉ cần tránh giải thích ý nghĩa của phân bổ tĩnh. Vì vậy, tôi sẽ giải thích ba hình thức phân bổ chính và cách chúng thường liên quan đến heap, stack và phân đoạn dữ liệu bên dưới. Tôi cũng sẽ hiển thị một số ví dụ trong cả C/C++ và Python để giúp mọi người hiểu.

Các biến "tĩnh" (AKA được phân bổ tĩnh) không được phân bổ trên ngăn xếp. Đừng cho là như vậy - nhiều người chỉ làm thế bởi vì "tĩnh" nghe rất giống "stack". Chúng thực sự tồn tại trong cả chồng và đống. Là một phần của cái được gọi là phân đoạn dữ liệu .

Tuy nhiên, nói chung là tốt hơn để xem xét "scope" và "life" thay vì "stack" và "heap".

Phạm vi đề cập đến những phần nào của mã có thể truy cập vào một biến. Nói chung, chúng tôi nghĩ về phạm vi cục bộ (chỉ có thể được truy cập bởi chức năng hiện tại) so với phạm vi toàn cầu (có thể được truy cập ở bất cứ đâu) mặc dù phạm vi có thể phức tạp hơn nhiều.

Trọn đời đề cập đến khi một biến được phân bổ và giải phóng trong quá trình thực hiện chương trình. Thông thường chúng ta nghĩ về phân bổ tĩnh (biến sẽ tồn tại trong toàn bộ thời lượng của chương trình, giúp việc lưu trữ cùng một thông tin qua một số cuộc gọi chức năng) so với phân bổ tự động (biến chỉ tồn tại trong một cuộc gọi cho một hàm, làm cho nó hữu ích cho việc lưu trữ thông tin chỉ được sử dụng trong hàm của bạn và có thể bị loại bỏ sau khi bạn hoàn thành) so với phân bổ động (các biến có thời lượng được xác định trong thời gian chạy, thay vì thời gian biên dịch như tĩnh hoặc tự động ).

Mặc dù hầu hết các trình biên dịch và trình thông dịch thực hiện hành vi này tương tự nhau về việc sử dụng ngăn xếp, đống, v.v., một trình biên dịch đôi khi có thể phá vỡ các quy ước này nếu nó muốn miễn là hành vi đó là chính xác. Ví dụ, do tối ưu hóa, một biến cục bộ chỉ có thể tồn tại trong một thanh ghi hoặc bị xóa hoàn toàn, mặc dù hầu hết các biến cục bộ tồn tại trong ngăn xếp. Như đã được chỉ ra trong một vài bình luận, bạn có thể tự do triển khai một trình biên dịch thậm chí không sử dụng một ngăn xếp hoặc một đống, mà thay vào đó là một số cơ chế lưu trữ khác (hiếm khi được thực hiện, vì ngăn xếp và đống rất tốt cho việc này).

Tôi sẽ cung cấp một số mã C chú thích đơn giản để minh họa tất cả điều này. Cách tốt nhất để học là chạy một chương trình theo trình gỡ lỗi và xem hành vi. Nếu bạn thích đọc python, bỏ qua đến cuối câu trả lời :)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

Một ví dụ đặc biệt sâu sắc về lý do tại sao việc phân biệt giữa tuổi thọ và phạm vi lại quan trọng là một biến có thể có phạm vi cục bộ nhưng tuổi thọ tĩnh - ví dụ: "someLocalStaticVariable" trong mẫu mã ở trên. Các biến như vậy có thể làm cho thói quen đặt tên phổ biến nhưng không chính thức của chúng ta rất khó hiểu. Chẳng hạn, khi chúng ta nói "cục bộ" chúng ta thường có nghĩa là "biến được phân bổ tự động trong phạm vi cục bộ" và khi chúng ta nói toàn cầu, chúng ta thường có nghĩa là "biến được phân bổ tĩnh trong phạm vi toàn cầu ". Thật không may khi nói đến những thứ như "tệp nằm trong phạm vi các biến được phân bổ tĩnh" nhiều người chỉ nói ... "huh ???".

Một số lựa chọn cú pháp trong C/C++ làm trầm trọng thêm vấn đề này - ví dụ, nhiều người cho rằng các biến toàn cục không phải là "tĩnh" do cú pháp được hiển thị bên dưới.

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

Lưu ý rằng việc đặt từ khóa "tĩnh" trong khai báo ở trên sẽ ngăn var2 có phạm vi toàn cầu. Tuy nhiên, var1 toàn cầu có phân bổ tĩnh. Điều này không trực quan! Vì lý do này, tôi cố gắng không bao giờ sử dụng Word "tĩnh" khi mô tả phạm vi, và thay vào đó nói một cái gì đó như phạm vi "tệp" hoặc "giới hạn tệp". Tuy nhiên, nhiều người sử dụng cụm từ "tĩnh" hoặc "phạm vi tĩnh" để mô tả một biến chỉ có thể được truy cập từ một tệp mã. Trong ngữ cảnh của thời gian tồn tại, "tĩnh" luôn luôn có nghĩa là biến được phân bổ khi bắt đầu chương trình và được giải phóng khi chương trình thoát.

Một số người nghĩ về các khái niệm này là C/C++ cụ thể. Họ không phải. Chẳng hạn, mẫu Python dưới đây minh họa cả ba loại phân bổ (có một số khác biệt tinh tế có thể có trong các ngôn ngữ được giải thích mà tôi sẽ không nhận được ở đây).

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __== "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones
174
davec

Những người khác đã trả lời các nét rộng khá tốt, vì vậy tôi sẽ đưa ra một vài chi tiết.

  1. Chồng và đống không cần phải là số ít. Một tình huống phổ biến trong đó bạn có nhiều ngăn xếp là nếu bạn có nhiều hơn một luồng trong một quy trình. Trong trường hợp này, mỗi luồng có ngăn xếp riêng của nó. Bạn cũng có thể có nhiều heap, ví dụ một số cấu hình DLL có thể dẫn đến các DLL khác nhau được phân bổ từ các heap khác nhau, đó là lý do tại sao nói chung là một ý tưởng tồi để giải phóng bộ nhớ được phân bổ bởi một thư viện khác.

  2. Trong C, bạn có thể nhận được lợi ích của việc phân bổ độ dài thay đổi thông qua việc sử dụng alloca , phân bổ trên ngăn xếp, trái ngược với phân bổ, phân bổ trên heap. Bộ nhớ này sẽ không tồn tại câu lệnh trả về của bạn, nhưng nó hữu ích cho bộ đệm đầu.

  3. Tạo một bộ đệm tạm thời khổng lồ trên Windows mà bạn không sử dụng nhiều không phải là miễn phí. Điều này là do trình biên dịch sẽ tạo ra một vòng lặp thăm dò ngăn xếp được gọi mỗi khi hàm của bạn được nhập để đảm bảo ngăn xếp tồn tại (vì Windows sử dụng một trang bảo vệ duy nhất ở cuối ngăn xếp của bạn để phát hiện khi nào nó cần tăng ngăn xếp. Nếu bạn truy cập bộ nhớ nhiều hơn một trang ở cuối ngăn xếp, bạn sẽ gặp sự cố). Thí dụ:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}
158
Don Neufeld

Những người khác đã trả lời trực tiếp câu hỏi của bạn, nhưng khi cố gắng hiểu stack và heap, tôi nghĩ sẽ hữu ích khi xem xét cách bố trí bộ nhớ của một quy trình UNIX truyền thống (không có chủ đề và mmap()- cấp phát dựa trên _). Thuật ngữ quản lý bộ nhớ trang web có sơ đồ bố trí bộ nhớ này.

Theo truyền thống, ngăn xếp và đống được đặt ở hai đầu đối diện của không gian địa chỉ ảo của quy trình. Ngăn xếp tự động phát triển khi được truy cập, lên đến kích thước được đặt bởi kernel (có thể được điều chỉnh bằng setrlimit(RLIMIT_STACK, ...)). Heap phát triển khi bộ cấp phát bộ nhớ gọi brk() hoặc sbrk() gọi hệ thống, ánh xạ nhiều trang bộ nhớ vật lý vào không gian địa chỉ ảo của quy trình. 

Trong các hệ thống không có bộ nhớ ảo, chẳng hạn như một số hệ thống nhúng, bố cục cơ bản tương tự thường được áp dụng, ngoại trừ ngăn xếp và đống được cố định về kích thước. Tuy nhiên, trong các hệ thống nhúng khác (chẳng hạn như các hệ thống dựa trên bộ vi điều khiển PIC Microchip), ngăn xếp chương trình là một khối bộ nhớ riêng không thể xử lý theo hướng dẫn di chuyển dữ liệu và chỉ có thể được sửa đổi hoặc đọc gián tiếp thông qua các hướng dẫn luồng chương trình (cuộc gọi, trở về, v.v.). Các kiến ​​trúc khác, chẳng hạn như bộ xử lý Intel Itanium, có nhiều ngăn xếp . Theo nghĩa này, ngăn xếp là một thành phần của kiến ​​trúc CPU.

127
bk1e

Ngăn xếp là một phần của bộ nhớ có thể được thao tác thông qua một số hướng dẫn ngôn ngữ hội chính, chẳng hạn như 'pop' (xóa và trả lại giá trị từ ngăn xếp) và 'Đẩy' (Đẩy giá trị vào ngăn xếp), nhưng cũng gọi ( gọi một chương trình con - điều này sẽ đẩy địa chỉ trở về ngăn xếp) và quay trở lại (trở về từ một chương trình con - điều này sẽ bật địa chỉ ra khỏi ngăn xếp và nhảy tới nó). Đó là vùng bộ nhớ bên dưới thanh ghi con trỏ ngăn xếp, có thể được đặt khi cần thiết. Ngăn xếp cũng được sử dụng để truyền các đối số cho các chương trình con và cũng để bảo toàn các giá trị trong các thanh ghi trước khi gọi các chương trình con.

Heap là một phần bộ nhớ được cung cấp cho một ứng dụng bởi hệ điều hành, thường là thông qua một tòa nhà cao tầng như malloc. Trên các hệ điều hành hiện đại, bộ nhớ này là một tập hợp các trang mà chỉ có quá trình gọi mới có quyền truy cập.

Kích thước của ngăn xếp được xác định trong thời gian chạy và thường không tăng sau khi chương trình khởi chạy. Trong một chương trình C, ngăn xếp cần đủ lớn để chứa mọi biến được khai báo trong mỗi hàm. Heap sẽ tăng trưởng linh hoạt khi cần, nhưng HĐH cuối cùng thực hiện cuộc gọi (nó thường sẽ tăng số lượng lớn hơn giá trị mà malloc yêu cầu, do đó, ít nhất một số mallocs trong tương lai sẽ không cần quay lại kernel lấy thêm bộ nhớ. Hành vi này thường có thể tùy chỉnh)

Vì bạn đã phân bổ ngăn xếp trước khi khởi chạy chương trình, bạn không bao giờ cần phải malloc trước khi bạn có thể sử dụng ngăn xếp, vì vậy đó là một lợi thế nhỏ ở đó. Trong thực tế, rất khó để dự đoán cái gì sẽ nhanh và cái gì sẽ chậm trong hệ điều hành hiện đại có hệ thống con bộ nhớ ảo, bởi vì cách các trang được triển khai và nơi chúng được lưu trữ là một chi tiết triển khai. 

108
Daniel Papasian

Tôi nghĩ rằng nhiều người khác đã cho bạn câu trả lời chính xác về vấn đề này.

Tuy nhiên, một chi tiết đã bị bỏ lỡ là "đống" trên thực tế có thể được gọi là "cửa hàng miễn phí". Lý do cho sự khác biệt này là cửa hàng miễn phí ban đầu được triển khai với cấu trúc dữ liệu được gọi là "đống nhị thức". Vì lý do đó, phân bổ từ việc triển khai sớm malloc ()/free () là phân bổ từ một đống. Tuy nhiên, trong thời hiện đại này, hầu hết các cửa hàng miễn phí được triển khai với các cấu trúc dữ liệu rất phức tạp không phải là đống nhị thức.

107
Heath

Chồng là gì?

Một ngăn xếp là một đống các đối tượng, thường là một đối tượng được sắp xếp gọn gàng.

 Enter image description here

Các ngăn xếp trong kiến ​​trúc điện toán là các vùng của bộ nhớ trong đó dữ liệu được thêm hoặc xóa theo cách cuối cùng trước xuất trước. 
Trong một ứng dụng đa luồng, mỗi luồng sẽ có ngăn xếp riêng.

Heap là gì?

Một đống là một bộ sưu tập những thứ không gọn gàng chất đống.

 Enter image description here

Trong các kiến ​​trúc điện toán, heap là một vùng bộ nhớ được cấp phát động được quản lý tự động bởi hệ điều hành hoặc thư viện trình quản lý bộ nhớ. 
Bộ nhớ trên heap được phân bổ, phân bổ và thay đổi kích thước thường xuyên trong khi thực hiện chương trình và điều này có thể dẫn đến một vấn đề gọi là phân mảnh. 
Sự phân mảnh xảy ra khi các đối tượng bộ nhớ được phân bổ với các khoảng trống nhỏ ở giữa quá nhỏ để chứa các đối tượng bộ nhớ bổ sung. 
Kết quả cuối cùng là một tỷ lệ phần trăm của không gian heap không thể sử dụng để phân bổ bộ nhớ thêm.

Cả hai cùng nhau

Trong một ứng dụng đa luồng, mỗi luồng sẽ có ngăn xếp riêng. Nhưng, tất cả các chủ đề khác nhau sẽ chia sẻ heap. 
Bởi vì các luồng khác nhau chia sẻ heap trong một ứng dụng đa luồng, điều này cũng có nghĩa là phải có sự phối hợp giữa các luồng để chúng không cố gắng truy cập và thao tác cùng (các) bộ nhớ trong heap tại cùng lúc.

Cái nào nhanh hơn - stack hay heap? Và tại sao?

Ngăn xếp nhanh hơn nhiều so với đống. 
Điều này là do cách bộ nhớ được phân bổ trên ngăn xếp. 
Phân bổ bộ nhớ trên ngăn xếp cũng đơn giản như di chuyển con trỏ ngăn xếp lên.

Đối với những người mới lập trình, nó có lẽ là một ý tưởng tốt để sử dụng stack vì nó dễ dàng hơn. 
Vì ngăn xếp nhỏ, bạn sẽ muốn sử dụng nó khi bạn biết chính xác bạn cần bao nhiêu bộ nhớ cho dữ liệu của mình hoặc nếu bạn biết kích thước dữ liệu của mình rất nhỏ. 
Tốt hơn hết là sử dụng heap khi bạn biết rằng bạn sẽ cần rất nhiều bộ nhớ cho dữ liệu của mình hoặc bạn không chắc chắn mình sẽ cần bao nhiêu bộ nhớ (như với một mảng động).

Mô hình bộ nhớ Java

 Enter image description here

Ngăn xếp là vùng bộ nhớ nơi lưu trữ các biến cục bộ (bao gồm các tham số phương thức). Khi nói đến các biến đối tượng, đây chỉ là các tham chiếu (con trỏ) đến các đối tượng thực tế trên heap.
Mỗi khi một đối tượng được khởi tạo, một đoạn bộ nhớ heap được đặt sang một bên để giữ dữ liệu (trạng thái) của đối tượng đó. Vì các đối tượng có thể chứa các đối tượng khác, trên thực tế, một số dữ liệu này có thể chứa các tham chiếu đến các đối tượng lồng nhau đó.

101
Shreyos Adikari

Bạn có thể làm một số điều thú vị với ngăn xếp. Chẳng hạn, bạn có các hàm như alloca (giả sử bạn có thể vượt qua các cảnh báo nghiêm trọng liên quan đến việc sử dụng nó), đây là một dạng malloc đặc biệt sử dụng stack, không phải heap, cho bộ nhớ.

Điều đó nói rằng, lỗi bộ nhớ dựa trên ngăn xếp là một trong những điều tồi tệ nhất tôi đã trải qua. Nếu bạn sử dụng bộ nhớ heap và bạn vượt qua giới hạn của khối được phân bổ, bạn có khả năng gây ra lỗi phân đoạn. (Không phải 100%: khối của bạn có thể tiếp giáp ngẫu nhiên với khối khác mà bạn đã phân bổ trước đó.) Nhưng vì các biến được tạo trên ngăn xếp luôn liền kề với nhau, việc viết ra khỏi giới hạn có thể thay đổi giá trị của biến khác. Tôi đã học được rằng bất cứ khi nào tôi cảm thấy rằng chương trình của tôi đã ngừng tuân theo các quy tắc logic, nó có thể là tràn bộ đệm.

87
Peter

Đơn giản, ngăn xếp là nơi các biến cục bộ được tạo. Ngoài ra, mỗi khi bạn gọi một chương trình con bộ đếm chương trình (con trỏ đến lệnh máy tiếp theo) và bất kỳ thanh ghi quan trọng nào, và đôi khi các tham số được đẩy trên ngăn xếp. Sau đó, bất kỳ biến cục bộ nào trong chương trình con được đẩy lên ngăn xếp (và được sử dụng từ đó). Khi chương trình con kết thúc, tất cả những thứ đó sẽ bật ra khỏi ngăn xếp. PC và dữ liệu đăng ký được và đưa trở lại vị trí của nó khi nó được bật lên, vì vậy chương trình của bạn có thể đi trên con đường vui vẻ của nó.

Heap là khu vực phân bổ bộ nhớ động bộ nhớ được thực hiện (các cuộc gọi "mới" hoặc "phân bổ" rõ ràng). Đây là một cấu trúc dữ liệu đặc biệt có thể theo dõi các khối bộ nhớ có kích thước khác nhau và trạng thái phân bổ của chúng.

Trong các hệ thống "cổ điển" RAM được đặt ra sao cho con trỏ ngăn xếp bắt đầu ở dưới cùng của bộ nhớ, con trỏ heap bắt đầu ở phía trên và chúng phát triển về phía nhau. Nếu chúng trùng nhau, bạn hết RAM. Điều đó không làm việc với các hệ điều hành đa luồng hiện đại. Mỗi luồng phải có ngăn xếp riêng và chúng có thể được tạo một cách linh hoạt.

84
T.E.D.

Từ WikiAnwser.

Cây rơm

Khi một hàm hoặc một phương thức gọi một hàm khác lần lượt gọi một hàm khác, v.v., việc thực thi tất cả các hàm đó vẫn bị treo cho đến khi hàm cuối cùng trả về giá trị của nó.

Chuỗi các lệnh gọi hàm bị treo này là ngăn xếp, bởi vì các phần tử trong ngăn xếp (các hàm gọi) phụ thuộc lẫn nhau.

Ngăn xếp là quan trọng để xem xét trong xử lý ngoại lệ và thực thi luồng.

Đống

Heap chỉ đơn giản là bộ nhớ được sử dụng bởi các chương trình để lưu trữ các biến . Phần tử của heap (biến) không có phụ thuộc với nhau và luôn có thể được truy cập ngẫu nhiên bất cứ lúc nào.

78
devXen

Cây rơm

  • Truy cập rất nhanh
  • Không phải phân bổ rõ ràng các biến
  • Không gian được quản lý hiệu quả bởi CPU, bộ nhớ sẽ không bị phân mảnh
  • Chỉ các biến cục bộ
  • Giới hạn kích thước ngăn xếp (phụ thuộc hệ điều hành)
  • Các biến không thể thay đổi kích thước

Heap

  • Các biến có thể được truy cập trên toàn cầu
  • Không giới hạn kích thước bộ nhớ
  • (Tương đối) truy cập chậm hơn
  • Không đảm bảo sử dụng hiệu quả không gian, bộ nhớ có thể bị phân mảnh theo thời gian khi các khối bộ nhớ được phân bổ, sau đó được giải phóng
  • Bạn phải quản lý bộ nhớ (bạn chịu trách nhiệm phân bổ và giải phóng các biến)
  • Các biến có thể được thay đổi kích thước bằng realloc ()
50
unknown

Nói ngắn gọn

Một ngăn xếp được sử dụng để cấp phát bộ nhớ tĩnh và một đống cho phân bổ bộ nhớ động, cả hai được lưu trữ trong RAM của máy tính.


Chi tiết

Ngăn xếp

Ngăn xếp là cấu trúc dữ liệu "LIFO" (cuối cùng vào trước ra trước), được CPU quản lý và tối ưu hóa khá chặt chẽ. Mỗi khi một hàm khai báo một biến mới, nó sẽ được "đẩy" lên ngăn xếp. Sau đó, mỗi khi một hàm thoát ra, tất cả các biến được đẩy lên ngăn xếp bởi hàm đó, sẽ được giải phóng (nghĩa là chúng bị xóa). Khi một biến ngăn xếp được giải phóng, vùng bộ nhớ đó sẽ khả dụng cho các biến ngăn xếp khác.

Ưu điểm của việc sử dụng ngăn xếp để lưu trữ các biến là bộ nhớ được quản lý cho bạn. Bạn không phải phân bổ bộ nhớ bằng tay hoặc giải phóng bộ nhớ một khi bạn không cần nó nữa. Hơn thế nữa, vì CPU tổ chức bộ nhớ ngăn xếp rất hiệu quả, việc đọc và ghi vào các biến ngăn xếp rất nhanh.

Có thể tìm thấy nhiều hơn tại đây.


Heap

Vùng heap là vùng bộ nhớ máy tính của bạn không được quản lý tự động cho bạn và không được CPU quản lý chặt chẽ. Đây là vùng bộ nhớ nổi tự do hơn (và lớn hơn). Để phân bổ bộ nhớ trên heap, bạn phải sử dụng malloc () hoặc calloc (), là các hàm C tích hợp. Khi bạn đã phân bổ bộ nhớ trên heap, bạn có trách nhiệm sử dụng free () để giải phóng bộ nhớ đó một khi bạn không cần nó nữa.

Nếu bạn không làm điều này, chương trình của bạn sẽ có cái được gọi là rò rỉ bộ nhớ. Đó là, bộ nhớ trên heap vẫn sẽ được đặt sang một bên (và sẽ không có sẵn cho các quy trình khác). Như chúng ta sẽ thấy trong phần gỡ lỗi, có một công cụ có tên Valgrind có thể giúp bạn phát hiện rò rỉ bộ nhớ.

Không giống như ngăn xếp, heap không có giới hạn kích thước đối với kích thước thay đổi (ngoài các giới hạn vật lý rõ ràng của máy tính của bạn). Bộ nhớ heap hơi chậm để đọc và ghi vào, bởi vì người ta phải sử dụng các con trỏ để truy cập bộ nhớ trên heap. Chúng tôi sẽ nói về con trỏ trong thời gian ngắn.

Không giống như ngăn xếp, các biến được tạo trên heap có thể được truy cập bởi bất kỳ hàm nào, bất cứ nơi nào trong chương trình của bạn. Biến Heap về cơ bản là toàn cầu trong phạm vi.

Có thể tìm thấy nhiều hơn tại đây.


Các biến được phân bổ trên ngăn xếp được lưu trữ trực tiếp vào bộ nhớ và truy cập vào bộ nhớ này rất nhanh và việc phân bổ của nó được xử lý khi chương trình được biên dịch. Khi một hàm hoặc một phương thức gọi một hàm khác lần lượt gọi một hàm khác, v.v., việc thực thi tất cả các hàm đó vẫn bị treo cho đến khi hàm cuối cùng trả về giá trị của nó. Ngăn xếp luôn được bảo lưu theo thứ tự LIFO, khối dành riêng gần đây nhất luôn là khối tiếp theo được giải phóng. Điều này làm cho việc theo dõi ngăn xếp thực sự đơn giản, giải phóng một khối khỏi ngăn xếp không gì khác hơn là điều chỉnh một con trỏ.

Các biến được phân bổ trên heap có bộ nhớ được cấp phát trong thời gian chạy và truy cập bộ nhớ này chậm hơn một chút, nhưng kích thước heap chỉ bị giới hạn bởi kích thước của bộ nhớ ảo. Các yếu tố của heap không có sự phụ thuộc lẫn nhau và luôn có thể được truy cập ngẫu nhiên bất cứ lúc nào. Bạn có thể phân bổ một khối bất cứ lúc nào và giải phóng nó bất cứ lúc nào. Điều này làm cho nó phức tạp hơn nhiều để theo dõi phần nào của heap được phân bổ hoặc miễn phí tại bất kỳ thời điểm nào.

Enter image description here

Bạn có thể sử dụng ngăn xếp nếu bạn biết chính xác lượng dữ liệu bạn cần phân bổ trước khi biên dịch thời gian và nó không quá lớn. Bạn có thể sử dụng heap nếu bạn không biết chính xác mình sẽ cần bao nhiêu dữ liệu trong thời gian chạy hoặc nếu bạn cần phân bổ nhiều dữ liệu.

Trong một tình huống đa luồng, mỗi luồng sẽ có ngăn xếp hoàn toàn độc lập của riêng mình, nhưng chúng sẽ chia sẻ heap. Ngăn xếp là luồng cụ thể và heap là ứng dụng cụ thể. Ngăn xếp là quan trọng để xem xét trong xử lý ngoại lệ và thực thi luồng.

Mỗi luồng có một ngăn xếp, trong khi thông thường chỉ có một đống cho ứng dụng (mặc dù không có nhiều đống cho các loại phân bổ khác nhau).

Enter image description here

Trong thời gian chạy, nếu ứng dụng cần nhiều heap, nó có thể phân bổ bộ nhớ từ bộ nhớ trống và nếu ngăn xếp cần bộ nhớ, nó có thể phân bổ bộ nhớ từ bộ nhớ cấp phát bộ nhớ cho ứng dụng.

Thậm chí, nhiều chi tiết hơn được đưa ra tại đây tại đây .


Bây giờ hãy đến câu trả lời của câu hỏi của bạn .

Chúng được điều khiển bởi hệ điều hành hoặc thời gian chạy ngôn ngữ ở mức độ nào?

HĐH phân bổ ngăn xếp cho từng luồng cấp hệ thống khi luồng được tạo. Thông thường, HĐH được gọi bởi bộ thực thi ngôn ngữ để phân bổ heap cho ứng dụng.

Có thể tìm thấy nhiều hơn tại đây.

Phạm vi của họ là gì?

Đã được đưa ra trong đầu.

"Bạn có thể sử dụng ngăn xếp nếu bạn biết chính xác mình cần phân bổ bao nhiêu dữ liệu trước khi biên dịch và nó không quá lớn. Bạn có thể sử dụng heap nếu bạn không biết chính xác mình sẽ cần bao nhiêu dữ liệu khi chạy hoặc nếu bạn cần phân bổ nhiều dữ liệu. "

Nhiều hơn có thể được tìm thấy trong ở đây .

Điều gì quyết định kích thước của mỗi người trong số họ?

Kích thước của ngăn xếp được đặt bởi OS khi một luồng được tạo. Kích thước của heap được đặt khi khởi động ứng dụng, nhưng nó có thể tăng lên khi cần dung lượng (bộ cấp phát yêu cầu thêm bộ nhớ từ hệ điều hành).

Điều gì làm cho một người nhanh hơn?

Phân bổ ngăn xếp nhanh hơn nhiều vì tất cả những gì nó thực sự làm là di chuyển con trỏ ngăn xếp. Sử dụng nhóm bộ nhớ, bạn có thể có được hiệu năng tương đương từ phân bổ heap, nhưng điều đó đi kèm với một sự phức tạp được thêm vào một chút và đau đầu của chính nó.

Ngoài ra, stack vs heap không chỉ là một xem xét hiệu suất; nó cũng cho bạn biết rất nhiều về tuổi thọ dự kiến ​​của các vật thể.

Chi tiết có thể được tìm thấy từ tại đây.

42
Abrar Jahin

OK, đơn giản và nói ngắn gọn, chúng có nghĩa là đã ra lệnhkhông được đặt hàng...!

Stack: Trong các mục stack, mọi thứ nằm trên đỉnh của nhau, có nghĩa là sẽ được xử lý nhanh hơn và hiệu quả hơn! ... 

Vì vậy, luôn có một chỉ mục để chỉ mục cụ thể, đồng thời xử lý sẽ nhanh hơn, cũng có mối quan hệ giữa các mục! ...

Heap: Không có thứ tự, quá trình xử lý sẽ chậm hơn và các giá trị bị rối tung cùng với không có thứ tự hoặc chỉ mục cụ thể ... không có ngẫu nhiên và không có mối quan hệ nào giữa chúng ... vì vậy thời gian thực hiện và sử dụng khác nhau ...

Tôi cũng tạo hình ảnh bên dưới để cho thấy chúng trông như thế nào:

 enter image description here

38
Alireza

Vào những năm 1980, UNIX đã tuyên truyền như những con thỏ với các công ty lớn tự mình . Exxon có một hàng chục thương hiệu bị mất trong lịch sử . Làm thế nào bộ nhớ được đặt ra theo quyết định của nhiều người thực hiện.

Một chương trình C điển hình đã được đặt sẵn trong bộ nhớ với cơ hội tăng lên bằng cách thay đổi giá trị brk () . Thông thường, HEAP ở ngay dưới giá trị brk này Và tăng brk tăng số lượng khả dụng đống.

STACK duy nhất thường là một khu vực bên dưới HEAP, là một bộ nhớ Không chứa gì có giá trị cho đến đỉnh của bộ nhớ cố định tiếp theo . Khối tiếp theo này thường là CODE có thể được ghi đè bằng dữ liệu ngăn xếpin một trong những hack nổi tiếng trong thời đại của nó.

Một khối bộ nhớ điển hình là BSS (một khối có giá trị 0) Vô tình không bị xóa trong cung cấp của một nhà sản xuất . Một khối khác là DATA chứa các giá trị khởi tạo, bao gồm cả chuỗi và số . (Thời gian chạy C), chính, chức năng và thư viện.

Sự ra đời của bộ nhớ ảo trong UNIX thay đổi nhiều ràng buộc . Không có lý do khách quan nào khiến các khối này cần phải liền kề nhau, Hoặc cố định về kích thước, hoặc đặt mua một cách cụ thể ngay bây giờ . Tất nhiên, trước đây UNIX là Multics không chịu những ràng buộc này . Đây là sơ đồ hiển thị một trong những bố cục bộ nhớ của thời đại đó.

A typical 1980s style UNIX C program memory layout

35
jlettvin

stack, heapdata của mỗi tiến trình trong bộ nhớ ảo:

 stack, heap and static data

29
Yousha Aleayoub

Một vài xu: Tôi nghĩ, sẽ rất tốt nếu vẽ đồ họa bộ nhớ và đơn giản hơn:

 This is my vision of process memory construction with simplification for more easy understanding wht happening


Mũi tên - hiển thị nơi tăng stack và heap, kích thước ngăn xếp xử lý có giới hạn, được xác định trong HĐH, giới hạn kích thước ngăn xếp luồng theo các tham số trong API tạo luồng thường. Heap thường giới hạn bằng cách xử lý kích thước bộ nhớ ảo tối đa, ví dụ trong 32 bit 2-4 GB.

Vì vậy, cách đơn giản: heap process là chung cho tiến trình và tất cả các luồng bên trong, sử dụng để cấp phát bộ nhớ trong trường hợp phổ biến với cái gì đó như malloc ().

Stack là bộ nhớ nhanh để lưu trữ trong các trường hợp con trỏ trả về hàm và biến chung, được xử lý như các tham số trong lệnh gọi hàm, biến hàm cục bộ.

24
Maxim Akristiniy

Vì một số câu trả lời đã gây khó chịu, tôi sẽ đóng góp mite của mình.

Đáng ngạc nhiên, không ai đã đề cập rằng nhiều ngăn xếp cuộc gọi (nghĩa là không liên quan đến số lượng luồng xử lý cấp hệ điều hành) không chỉ được tìm thấy trong các ngôn ngữ kỳ lạ (PostScript) hoặc nền tảng (Intel Itanium), mà còn trong sợi , chủ đề xanh và một số triển khai của coroutines .

Sợi, sợi màu xanh lá cây và coroutines theo nhiều cách tương tự nhau, dẫn đến nhiều nhầm lẫn. Sự khác biệt giữa các sợi và các sợi màu xanh lá cây là cái trước sử dụng đa nhiệm hợp tác, trong khi cái sau có thể có tính năng hợp tác hoặc phủ đầu (hoặc thậm chí cả hai). Để phân biệt giữa sợi và coroutines, xem tại đây .

Trong mọi trường hợp, mục đích của cả sợi, luồng màu xanh lá cây và coroutines đều có nhiều chức năng thực thi đồng thời, nhưng không song song (xem này SO câu hỏi để phân biệt) trong một luồng cấp hệ điều hành duy nhất, chuyển điều khiển qua lại với nhau theo kiểu có tổ chức.

Khi sử dụng sợi, luồng màu xanh lá cây hoặc coroutines, bạn thường có một ngăn xếp riêng cho mỗi chức năng. (Về mặt kỹ thuật, không chỉ là một ngăn xếp mà là toàn bộ bối cảnh thực hiện cho mỗi chức năng. Quan trọng nhất là các thanh ghi CPU.) Đối với mỗi luồng có nhiều ngăn xếp như có các hàm đang chạy đồng thời và luồng đang chuyển đổi giữa việc thực hiện từng chức năng theo logic của chương trình của bạn. Khi một chức năng chạy đến cuối, ngăn xếp của nó bị phá hủy. Vì vậy, số lượng và thời gian sống của ngăn xếp là động và không được xác định bởi số lượng luồng cấp độ hệ điều hành!

Lưu ý rằng tôi đã nói " thường có ngăn xếp riêng cho mỗi chức năng". Có cả hai stackful stackless triển khai các sân. Các triển khai C++ xếp chồng đáng chú ý nhất là Boost.CoroutineMicrosoft PPL 's async/await. (Tuy nhiên, các chức năng có thể phục hồi của C++ (a.k.a. "asyncawait"), được đề xuất cho C++ 17, có khả năng sử dụng các coroutine không có chồng.)

Sắp có đề xuất sợi cho thư viện chuẩn C++. Ngoài ra, có một số bên thứ ba thư viện . Chủ đề màu xanh lá cây cực kỳ phổ biến trong các ngôn ngữ như Python và Ruby.

21
shakurov

Tôi có một cái gì đó để chia sẻ, mặc dù những điểm chính đã được bảo hiểm.

Cây rơm  

  • Truy cập rất nhanh.
  • Được lưu trữ trong RAM.
  • Các lệnh gọi hàm được tải ở đây cùng với các biến cục bộ và các tham số hàm được truyền.
  • Không gian được giải phóng tự động khi chương trình đi ra khỏi phạm vi.
  • Được lưu trữ trong bộ nhớ tuần tự.

Heap

  • Truy cập chậm so với Stack.
  • Được lưu trữ trong RAM.
  • Các biến được tạo động được lưu trữ ở đây, sau này yêu cầu giải phóng bộ nhớ được phân bổ sau khi sử dụng.
  • Được lưu trữ bất cứ nơi nào cấp phát bộ nhớ được thực hiện, truy cập bằng con trỏ luôn.

Lưu ý thú vị:

  • Nếu các lệnh gọi hàm đã được lưu trong heap, nó sẽ dẫn đến 2 điểm lộn xộn:
    1. Do lưu trữ tuần tự trong ngăn xếp, thực hiện nhanh hơn. Lưu trữ trong heap sẽ dẫn đến tiêu thụ thời gian rất lớn, do đó làm cho toàn bộ chương trình thực hiện chậm hơn.
    2. Nếu các hàm được lưu trữ trong heap (lưu trữ lộn xộn được chỉ bởi con trỏ), sẽ không có cách nào để quay lại địa chỉ người gọi trở lại (ngăn xếp này cung cấp do lưu trữ tuần tự trong bộ nhớ).
12
Pankaj Kumar Thapa

Rất nhiều câu trả lời đúng như các khái niệm, nhưng chúng ta phải lưu ý rằng phần cứng là cần thiết bởi phần cứng (tức là bộ vi xử lý) để cho phép gọi chương trình con (CALL bằng ngôn ngữ hội ..). (Những người OOP sẽ gọi nó là phương thức)

Trên ngăn xếp, bạn lưu địa chỉ trả về và gọi → Đẩy/lùi → pop được quản lý trực tiếp trong phần cứng.

Bạn có thể sử dụng ngăn xếp để truyền tham số .. ngay cả khi nó chậm hơn so với sử dụng các thanh ghi (một guru vi xử lý sẽ nói hoặc một cuốn sách BIOS tốt trong những năm 1980 ...)

  • Không có stack no vi xử lý có thể hoạt động. (chúng ta không thể tưởng tượng một chương trình, ngay cả trong ngôn ngữ hội, không có chương trình con/hàm)
  • Nếu không có đống nó có thể. (Một chương trình ngôn ngữ hội có thể hoạt động mà không có, vì heap là một khái niệm hệ điều hành, như malloc, đó là một cuộc gọi OS/Lib.

Sử dụng ngăn xếp nhanh hơn như:

  • Là phần cứng, và thậm chí Push/pop rất hiệu quả.
  • malloc yêu cầu vào chế độ kernel, sử dụng khóa/semaphore (hoặc các nguyên hàm đồng bộ hóa khác) thực thi một số mã và quản lý một số cấu trúc cần thiết để theo dõi phân bổ.
8
ingconti

Ồ Rất nhiều câu trả lời và tôi không nghĩ một trong số họ trả lời đúng ...

1) Chúng ở đâu và là gì (vật lý trong bộ nhớ của máy tính thật)?

Ngăn xếp là bộ nhớ bắt đầu là địa chỉ bộ nhớ cao nhất được phân bổ cho hình ảnh chương trình của bạn và sau đó nó giảm giá trị từ đó. Nó được dành riêng cho các tham số hàm được gọi và cho tất cả các biến tạm thời được sử dụng trong các hàm.

Có hai đống: công khai và riêng tư.

Heap riêng bắt đầu trên ranh giới 16 byte (đối với chương trình 64 bit) hoặc ranh giới 8 byte (đối với chương trình 32 bit) sau byte mã cuối cùng trong chương trình của bạn, sau đó tăng giá trị từ đó. Nó cũng được gọi là heap mặc định.

Nếu heap riêng quá lớn, nó sẽ chồng lên vùng stack, cũng như stack sẽ chồng lên heap nếu nó quá lớn. Bởi vì ngăn xếp bắt đầu ở một địa chỉ cao hơn và đi xuống địa chỉ thấp hơn, với việc hack đúng cách, bạn có thể làm cho ngăn xếp lớn đến mức nó sẽ tràn ngập vùng heap riêng và chồng lấp vùng mã. Thủ thuật sau đó là chồng lấp đủ vùng mã mà bạn có thể móc vào mã. Đó là một chút khó khăn để làm và bạn có nguy cơ sụp đổ chương trình, nhưng nó dễ dàng và rất hiệu quả.

Heap công cộng nằm trong không gian bộ nhớ riêng bên ngoài không gian hình ảnh chương trình của bạn. Chính bộ nhớ này sẽ được rút ra trên đĩa cứng nếu tài nguyên bộ nhớ bị khan hiếm.

2) Chúng được điều khiển bởi hệ điều hành hoặc thời gian chạy ngôn ngữ ở mức độ nào?

Ngăn xếp được kiểm soát bởi lập trình viên, heap riêng được quản lý bởi HĐH và heap công cộng không bị ai kiểm soát vì đây là dịch vụ HĐH - bạn đưa ra yêu cầu và chúng được cấp hoặc từ chối.

2b) Phạm vi của họ là gì?

Tất cả đều là chương trình toàn cầu, nhưng nội dung của họ có thể là riêng tư, công khai hoặc toàn cầu.

2c) Điều gì quyết định kích thước của mỗi người trong số họ?

Kích thước của ngăn xếp và heap riêng được xác định bởi các tùy chọn thời gian chạy trình biên dịch của bạn. Heap công khai được khởi tạo trong thời gian chạy bằng tham số kích thước.

2d) Điều gì làm cho một người nhanh hơn?

Chúng không được thiết kế để nhanh, chúng được thiết kế để có ích. Cách lập trình viên sử dụng chúng xác định xem chúng "nhanh" hay "chậm"

REF:

https://norasandler.com/2019/02/18/Write-a-Compiler-10.html

https://docs.Microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-get Processheap

https://docs.Microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate

3
ar18

chiến lược phân bổ lưu trữ theo ngôn ngữ lập trình 

BỘ NHỚ Ở C - THE STACK, HEAP, và STATIC

 enter image description here  MEMORY IN C – THE STACK, THE HEAP, AND STATIC

  1. OP: Họ kiểm soát hệ điều hành hoặc thời gian chạy ngôn ngữ ở mức độ nào?

Tôi muốn thêm một vài điều về câu hỏi quan trọng này: 

Hệ điều hành và ngôn ngữ chung 

Các thành phần chính của .NET Framework  enter image description here Kiến trúc khung 4.5  enter image description here

Các thành phần của CLR  Components of CLR  enter image description here  enter image description here  enter image description here

0
leonidaa