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


8105

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 ngăn xếp 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 đã không đọc một lời giải thích rõ ràng về điều này. Tôi hiểu thế nào là một ngăn xếp . Nhưng,

  • Chúng ở đâu và ở đâu (vật lý trong bộ nhớ của máy tính thật)?
  • 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?
  • 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?

175
một lời giải thích thực sự tốt có thể được tìm thấy ở đây Sự khác biệt giữa một chồng và một đống?
Songo

12
Ngoài ra (thực sự) tốt: codeproject.com/Articles/76153/, (phần stack / heap)
Ben


3
Liên quan, xem Stack Clash . Các sửa chữa Stack Clash ảnh hưởng đến một số khía cạnh của các biến và hành vi hệ thống như thế nào rlimit_stack. Xem thêm Red Hat Issue 1463241
jww

3
@mattshane Các định nghĩa về stack và heap không phụ thuộc vào giá trị và kiểu tham chiếu nào. Nói cách khác, stack và heap có thể được xác định đầy đủ ngay cả khi giá trị và kiểu tham chiếu không bao giờ tồn tại. Hơn nữa, khi hiểu giá trị và các loại tham chiếu, ngăn xếp chỉ là một chi tiết thực hiện. Per Eric Lippert: Stack là một chi tiết thực hiện, Phần một .
Matthew

Câu trả lời:


5966

Ngăn xếp là bộ nhớ được đặt sang một bên làm không gian đầu cho một chuỗi 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 (lần cuối 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 thực thi 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:

Ở 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ữ?

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 không gian (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 vì mẫu truy cập khiến 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 đượ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


74
Câu trả lời hay - nhưng tôi nghĩ bạn nên thêm rằng trong khi ngăn xếp được phân bổ bởi HĐH khi quá trình bắt đầu (giả sử sự tồn tại của HĐH), thì chương trình được duy trì nội tuyến. Đây là một lý do khác khiến ngăn xếp nhanh hơn, cũng như các hoạt động đẩy và pop thường là một lệnh máy và các máy hiện đại có thể thực hiện ít nhất 3 trong số chúng trong một chu kỳ, trong khi phân bổ hoặc giải phóng đống liên quan đến việc gọi mã OS.
sqykly

276
Tôi thực sự bối rối bởi sơ đồ ở cuối. Tôi nghĩ rằng tôi đã nhận được nó cho đến khi tôi nhìn thấy hình ảnh đó.
Sina Madani

10
@Anarelle bộ xử lý chạy các hướng dẫn có hoặc không có os. Một ví dụ gần với trái tim tôi là SNES, không có lệnh gọi API, không có HĐH như chúng ta biết ngày nay - nhưng nó có một ngăn xếp. Phân bổ trên một ngăn xếp là phép cộng và phép trừ trên các hệ thống này và điều đó tốt cho các biến bị phá hủy khi chúng được bật bằng cách trả về từ hàm đã tạo ra chúng, nhưng hiểu rằng, một nhà xây dựng, trong đó kết quả không thể chỉ là vứt đi. Cho rằng chúng ta cần đống, không bị ràng buộc để gọi và trả lại. Hầu hết các hệ điều hành đều có API một đống, không có lý do gì để tự làm điều đó
sqykly

2
"Ngăn xếp là bộ nhớ được đặt sang một bên như không gian đầu". Mát mẻ. Nhưng nó thực sự được "đặt sang một bên" về mặt cấu trúc bộ nhớ Java ?? Có phải bộ nhớ Heap / Bộ nhớ không heap / Khác (cấu trúc bộ nhớ Java theo betsol.com/2017/06/NH )
Jatin Shashoo 22/07/18

4
@JatinShashoo Thời gian chạy Java, với tư cách là trình thông dịch mã byte, thêm một cấp độ ảo hóa nữa, vì vậy những gì bạn đề cập đến chỉ là quan điểm ứng dụng Java. Từ quan điểm của hệ điều hành, tất cả chỉ là một đống, trong đó quy trình thời gian chạy Java phân bổ một phần không gian của nó làm bộ nhớ "không phải heap" cho mã byte được xử lý. Phần còn lại của heap cấp hệ điều hành đó được sử dụng như heap cấp ứng dụng, nơi dữ liệu của đối tượng được lưu trữ.
kbec

2350

Cây rơm:

  • Được lưu trữ trong RAM máy tính giống như đống.
  • 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ó một ngăn xếp tràn 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 RAM máy tính 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 trả tự do 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ổ bằng newhoặcmalloc tương ứng.
  • Có thể có lỗi phân bổ nếu yêu cầu quá lớn của 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;

31
Con trỏ pBuffer và giá trị của b được đặt trên ngăn xếp và hầu hết có khả năng được phân bổ ở lối vào hàm. Tùy thuộc vào trình biên dịch, bộ đệm cũng có thể được phân bổ tại lối vào chức năng.
Andy

36
Một quan niệm sai lầm phổ biến là Cngôn ngữ, như được định nghĩa bởi C99tiêu chuẩn ngôn ngữ (có sẵn tại open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), yêu cầu một "ngăn xếp". Trên thực tế, từ 'stack' thậm chí không xuất hiện trong tiêu chuẩn. Câu trả lời này Cnói chung việc sử dụng ngăn xếp của wrt / to là đúng, nhưng ngôn ngữ không yêu cầu. Xem knosof.co.uk/cbook/cbook.html để biết thêm thông tin và cụ thể cách Ctriển khai trên các kiến ​​trúc bóng lẻ như en.wikipedia.org/wiki/Burroughs_large_systems
johne

55
@Brian Bạn nên giải thích tại sao bộ đệm [] và con trỏ pBuffer được tạo trên ngăn xếp và tại sao dữ liệu của pBuffer được tạo trên heap. Tôi nghĩ rằng một số ppl có thể bị nhầm lẫn bởi câu trả lời của bạn vì họ có thể nghĩ rằng chương trình đang hướng dẫn cụ thể rằng bộ nhớ được phân bổ trên stack so với heap nhưng đây không phải là trường hợp. Có phải vì Buffer là loại giá trị trong khi pBuffer là loại tham chiếu?
Howiecamp

9
@Remover: Không có con trỏ giữ địa chỉ và nó có thể trỏ đến một cái gì đó trên heap hoặc stack. mới, malloc và một số chức năng khác tương tự như malloc phân bổ trên heap và trả về địa chỉ của bộ nhớ đã được phân bổ. Tại sao bạn muốn phân bổ trên heap? Vì vậy, bộ nhớ của bạn sẽ không vượt quá phạm vi và được giải phóng cho đến khi bạn muốn nó.
Brian R. Bondy

35
"Chịu trách nhiệm về rò rỉ bộ nhớ" - Heaps không chịu trách nhiệm về rò rỉ bộ nhớ! Những lập trình viên / lập trình viên lười biếng / Quên đi / java không đưa ra một tào lao là!
Laz

1370

Đ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).

    Chồng như chồng giấy

    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 như một đống cam thảo 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 tác 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 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 cấp phát động, thường được xử lý bởi mã thời gian chạy của ngôn ngữ lập trình hoặc môi trường được sử dụng.

  • 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ể thêm 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, mà bộ xử lý dịch sang các đị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ề. 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?

    Ngăn xếp nhanh hơn vì tất cả bộ nhớ trống luôn liền kề nhau. Không có danh sách nào cần được duy trì trong tất cả các phân đoạn của bộ nhớ trống, chỉ cần một con trỏ duy nhất đến đỉnh hiện tại của ngăn xếp. Trình biên dịch thường lưu trữ con trỏ này trong một đăng ký đặc biệt, nhanh chóng cho mục đích này. Hơn nữa, các hoạt động tiếp theo trên một ngăn xếp thường được tập trung trong các khu vực rất gần của bộ nhớ, ở mức rất thấp sẽ tốt cho việc tối ưu hóa bộ đệm khi chết của bộ xử lý.


20
David Tôi không đồng ý rằng đó là một hình ảnh tốt hoặc "ngăn xếp đẩy xuống" là một thuật ngữ tốt để minh họa khái niệm này. Khi bạn thêm một cái gì đó vào ngăn xếp, các nội dung khác của ngăn xếp sẽ không bị đẩy xuống, chúng vẫn giữ nguyên vị trí của chúng.
thomasrutter

8
Câu trả lời này bao gồm một sai lầm lớn. Các biến tĩnh không được phân bổ trên ngăn xếp. Xem câu trả lời của tôi [link] stackoverflow.com/a/13326916/1763801 để làm rõ. bạn đang đánh đồng các biến "tự động" với các biến "tĩnh", nhưng chúng hoàn toàn không giống nhau
davec

13
Cụ thể, bạn nói "biến cục bộ được phân bổ tĩnh" được phân bổ trên ngăn xếp. Trên thực tế, chúng được phân bổ trong phân khúc dữ liệu. Chỉ các biến được phân bổ tự động (bao gồm hầu hết nhưng không phải tất cả các biến cục bộ và cả những thứ như tham số hàm được truyền theo giá trị thay vì tham chiếu) được phân bổ trên ngăn xếp.
davec

9
Tôi vừa nhận ra mình đúng - trong C, phân bổ tĩnh là thứ riêng biệt của nó chứ không phải là một thuật ngữ cho bất cứ thứ gì không động . Tôi đã chỉnh sửa câu trả lời của mình, cảm ơn.
thomasrutter

5
Không chỉ C. Java, Pascal, Python và nhiều người khác đều có khái niệm phân bổ tĩnh so với tự động so với động. Nói "phân bổ tĩnh" có nghĩa là điều tương tự chỉ có ở mọi nơi. Trong ngôn ngữ không có phân bổ tĩnh có nghĩa là "không năng động". Bạn muốn phân bổ "tự động" cho những gì bạn đang mô tả (tức là những thứ trên ngăn xếp).
davec

727

(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 newhoặcmalloc ) đượ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. Thông tin meta này 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. Vì vậy, bạn có thể nghĩ về đống như một đống 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. Cái 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.

Đống

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 là 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ó hướng dẫn đặc biệt cho đẩy giá trị vào stack và popping chúng trở lại từ đống. Mỗi lần đẩ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. Một cửa sổ bật lên lấy giá trị được trỏ bởi con trỏ ngăn xếp và sau đó tăng con trỏ ngăn xếp (đừng nhầm lẫn bởi thực tế là việc thêm một giá trị vào ngăn xếp sẽ làm giảm con trỏ ngăn xếp và loại bỏ một giá trị làm tăng nó. phía dưới). 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 con trỏ lệnh hiện tại , 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ũ sẽ xuất hiện từ ngăn xếp và thực thi lại 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 sẽ đượ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à các bản ghi kích hoạt này 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 lỗi 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.

Chồng

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 mallocvà C ++ sử dụngnew , 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.


35
@Martin - Một câu trả lời / giải thích rất tốt hơn câu trả lời được chấp nhận trừu tượng hơn. Một chương trình lắp ráp mẫu hiển thị các con trỏ / thanh ghi ngăn xếp đang được sử dụng cho một lệnh gọi hàm vis sẽ mang tính minh họa hơn.
Bikal Lem

3
Mỗi loại tham chiếu là thành phần của các loại giá trị (int, chuỗi, v.v.). Như đã nói, các loại giá trị được lưu trữ trong ngăn xếp so với cách nó hoạt động khi chúng là một phần của loại tham chiếu.
Nps

15
Câu trả lời này là tốt nhất theo quan điểm của tôi, bởi vì nó giúp tôi hiểu được tuyên bố trả lại thực sự là gì và nó liên quan đến "địa chỉ trả lại" này như thế nào mà tôi bắt gặp mỗi lần, sau đó, ý nghĩa của việc đẩy một chức năng lên ngăn xếp, và tại sao các chức năng được đẩy lên ngăn xếp. Câu trả lời chính xác!
Alex

3
Đây là điều tốt nhất theo ý kiến ​​của tôi, cụ thể là để đề cập rằng heap / stack rất cụ thể. Các câu trả lời khác giả định rất nhiều điều về ngôn ngữ và môi trường / HĐH. +1
Qix - MONICA ĐƯỢC PHÂN PHỐI

2
Ý bạn là gì "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." ? Bạn có thể giải thích về điều này xin vui lòng?
Koray Tugay

404

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ớ

Hình ảnh các biến trên ngăn xếp

Local Variableschỉ 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 phân bổ bộ nhớ stack và heap «timmurphy.org

và đây:

Tạo các đố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.


15
Điều này là không chính xác. i và cls không phải là biến "tĩnh". chúng được gọi là biến "cục bộ" hoặc "tự động". Đó là một sự phân biệt rất quan trọng. Xem [link] stackoverflow.com/a/13326916/1763801 để làm rõ
davec

9
Tôi không nói rằng chúng là các biến tĩnh . Tôi đã nói rằng int và cls1 là các mục tĩnh . Bộ nhớ của chúng được phân bổ tĩnh và do đó chúng đi trên ngăn xếp. Điều này trái ngược với một đối tượng đòi hỏi cấp phát bộ nhớ động do đó đi vào heap.
Snowcrash

12
Tôi trích dẫn "Các mục tĩnh ... đi trên ngăn xếp". Đây chỉ là sai lầm. Các mục tĩnh đi trong phân đoạn dữ liệu, các mục tự động đi trên ngăn xếp.
davec

14
Ngoài ra, bất cứ ai đã viết bài báo về mật mã đó đều không biết anh ta đang nói về cái gì. Chẳng hạn, ông nói "những người nguyên thủy cần bộ nhớ loại tĩnh" hoàn toàn không đúng sự thật. Không có gì ngăn bạn phân bổ các nguyên thủy trong heap một cách linh hoạt, chỉ cần viết một cái gì đó như "int mảng [] = new int [num]" và voila, các nguyên thủy được phân bổ động trong .NET. Đó chỉ là một trong một số điểm không chính xác.
davec

8
Tôi đã chỉnh sửa bài viết của bạn vì bạn đã phạm sai lầm kỹ thuật nghiêm trọng về những gì diễn ra trong ngăn xếp và đống.
Tom Leys

209

Chồng 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 bên trong hàm của bạn, 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 mà 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à một tên chung cho nơi bạn đặt dữ liệu mà bạn tạo ra một cách nhanh chóng. 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 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).

Triển khai 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 (ngay cả 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.


16
Một khuyến nghị để tránh sử dụng heap là khá mạnh. Các hệ thống hiện đại có các trình quản lý heap tốt và các ngôn ngữ động hiện đại sử dụng heap rộng rãi (không có lập trình viên thực sự lo lắng về điều đó). Tôi muốn sử dụng heap, nhưng với một công cụ cấp phát thủ công, đừng quên miễn phí!
Greg Hewgill

2
Nếu bạn có thể sử dụng ngăn xếp hoặc đống, sử dụng ngăn xếp. Nếu bạn không thể sử dụng ngăn xếp, thực sự không có lựa chọn nào. Tôi sử dụng cả hai rất nhiều, và tất nhiên sử dụng std :: vector hoặc tương tự đạt được heap. Đối với một người mới, bạn tránh được đống vì đơn giản là rất dễ dàng !!
Tom Leys

Nếu ngôn ngữ của bạn không triển khai bộ sưu tập rác, Con trỏ thông minh (Các đối tượng được phân bổ hợp lý bao quanh một con trỏ đếm tham chiếu cho các khối bộ nhớ được phân bổ động) có liên quan chặt chẽ đến việc thu gom rác và là một cách tốt để quản lý đống trong an toàn và rò rỉ cách miễn phí. Chúng được thực hiện trong các khuôn khổ khác nhau, nhưng cũng không khó để thực hiện cho các chương trình của riêng bạn.
BenPen

1
"Đây là lý do tại sao nên tránh đống (mặc dù nó vẫn thường được sử dụng)." Tôi không chắc điều này thực tế có nghĩa là gì, đặc biệt là khi bộ nhớ được quản lý khác nhau trong nhiều ngôn ngữ cấp cao. Vì câu hỏi này được gắn thẻ không biết ngôn ngữ, tôi muốn nói nhận xét / dòng cụ thể này không đúng chỗ và không áp dụng được.
LintfordPickle

2
Điểm hay @JonnoHampson - Trong khi bạn đưa ra quan điểm hợp lệ, tôi sẽ lập luận rằng nếu bạn đang làm việc trong một "ngôn ngữ cấp cao" với một GC, bạn có thể không quan tâm đến các cơ chế phân bổ bộ nhớ - và vì vậy đừng thậm chí quan tâm ngăn xếp và đống là gì.
Tom Leys

194

Để 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 đống, ngăn xếp 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. Phần này là một phần của phân đoạn dữ liệu .

Tuy nhiên, nói chung là tốt hơn để xem xét " phạm vi " và " trọn đời " thay vì "chồng" và "đống".

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, làm cho nó hữu ích cho 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 lệnh gọi đến một hàm, làm cho nó hữu ích cho lưu trữ thông tin chỉ được sử dụng trong chức năng 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ì các 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 điều quan trọng để phân biệt giữa tuổi thọ và phạm vi 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. Ví dụ, 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 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 trên phạm vi toàn cầu ". Thật không may khi nói đến những thứ như " tập tin được phân bổ 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 từ "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 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 __name__ == "__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

Tôi sẽ đề cập đến một biến tĩnh được khai báo trong một hàm là chỉ có khả năng truy cập cục bộ , nhưng nói chung sẽ không sử dụng thuật ngữ "phạm vi" với nó. Ngoài ra, có thể đáng chú ý rằng khía cạnh một ngăn xếp / heap với ngôn ngữ về cơ bản là không linh hoạt: một ngôn ngữ lưu ngữ cảnh thực thi trên ngăn xếp không thể sử dụng cùng ngăn xếp đó để giữ những thứ cần thiết để tồn tại lâu hơn các bối cảnh trong đó chúng được tạo ra . Một số ngôn ngữ như PostScriptcó nhiều ngăn xếp, nhưng có một "đống" hoạt động giống như một ngăn xếp.
supercat

@supercat Điều đó có ý nghĩa. Tôi đã định nghĩa phạm vi là "phần nào của mã có thể truy cập vào một biến" (và cảm thấy đây là định nghĩa chuẩn nhất) vì vậy tôi nghĩ chúng tôi đồng ý :)
davec 17/12/13

Tôi sẽ coi "phạm vi" của một biến là bị giới hạn bởi thời gian cũng như không gian. Một biến ở phạm vi đối tượng lớp được yêu cầu giữ giá trị của nó miễn là đối tượng tồn tại. Một biến trong phạm vi bối cảnh thực thi được yêu cầu để giữ giá trị của nó miễn là thực thi vẫn còn trong bối cảnh đó. Một khai báo biến tĩnh tạo ra một mã định danh có phạm vi được giới hạn trong khối hiện tại, được gắn với một biến có phạm vi không bị ràng buộc.
supercat

@supercat Đây là lý do tại sao tôi sử dụng từ trọn đời, đó là cách tôi gọi thuật ngữ phạm vi thời gian bạn gọi. Nó làm giảm nhu cầu quá tải từ "phạm vi" với rất nhiều ý nghĩa. Theo như tôi có thể nói, dường như không có sự đồng thuận hoàn toàn về các định nghĩa chính xác, ngay cả trong các nguồn chính tắc. Thuật ngữ của tôi được rút ra một phần từ K & R và một phần từ việc sử dụng phổ biến tại bộ phận CS đầu tiên tôi nghiên cứu / giảng dạy tại. Luôn luôn tốt để nghe một quan điểm thông báo khác.
davec

1
bạn đang đùa chắc. bạn thực sự có thể định nghĩa biến tĩnh bên trong một hàm không?
Zaeem Sattar

168

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

Re "trái ngược với phân bổ": Ý của bạn là "trái ngược với malloc"?
Peter Mortensen

Làm thế nào là di động alloca?
Peter Mortensen

@PeterMortensen không phải là POSIX, tính di động không được đảm bảo.
Don Neufeld

135

Những người khác đã trực tiếp trả lời 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 bố cục bộ nhớ của một quy trình UNIX truyền thống (không có các luồng và mmap()phân bổ dựa trên nền tảng). Các Memory Management Thuật ngữ trang web có một 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.


117

Một 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.

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

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.

Một đống 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.

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

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ổ, giải phóng 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, có lẽ nên sử dụng stack vì nó dễ 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 dung lượng bộ nhớ bạn cần 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

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

Ngăn xếp là vùng bộ nhớ nơi các biến cục bộ (bao gồm các tham số phương thức) được lưu trữ. 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 khối 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 đó.


115

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ữ lắp ráp 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 đối số cho 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 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ố heap nhiều 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.


2
Cũng đáng đề cập ở đây rằng intel tối ưu hóa rất nhiều truy cập ngăn xếp, đặc biệt là những thứ như dự đoán nơi bạn trở về từ một chức năng.
Tom Leys

113

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.


8
Một câu trả lời khác - hầu hết các câu trả lời (nhẹ) ngụ ý rằng việc sử dụng "ngăn xếp" là bắt buộc bởi Cngôn ngữ. Đây là một quan niệm sai lầm phổ biến, mặc dù đó là mô hình thống trị (cho đến nay) để thực hiện C99 6.2.4 automatic storage duration objects(các biến). Trên thực tế, từ "stack" thậm chí không xuất hiện trong C99tiêu chuẩn ngôn ngữ: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
johne

[@ Dưới đây] Tôi có một nhận xét nhỏ về câu trả lời của bạn. Hãy xem câu trả lời được chấp nhận cho câu hỏi này . Nó nói rằng cửa hàng miễn phí có lẽ giống như đống , mặc dù không nhất thiết phải như vậy.
OmarOthman

91

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 chức năng 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ể tình cờ tiếp giáp 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.


Làm thế nào là di động alloca? Chẳng hạn, nó có hoạt động trên Windows không? Có phải nó chỉ dành cho các hệ điều hành giống Unix?
Peter Mortensen

89

Đơ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 là 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 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.


[@TED] Tại sao bạn nói "đôi khi các tham số được đẩy lên ngăn xếp"? Những gì tôi biết là họ luôn luôn như vậy. Bạn có thể vui lòng giải thích thêm?
OmarOthman

1
@OmarOthman - Tôi nói vậy bởi vì nó hoàn toàn phụ thuộc vào người viết trình biên dịch / trình thông dịch của bạn, điều gì xảy ra khi một chương trình con được gọi. Hành vi cổ điển của Fortran là không sử dụng một ngăn xếp nào cả. Một số ngôn ngữ hỗ trợ những thứ kỳ lạ như pass-by-name, đây thực sự là một sự thay thế văn bản.
TED

83

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 lẫn nhau và luôn có thể được truy cập ngẫu nhiên bất cứ lúc nào.


"Tôi thích câu trả lời được chấp nhận tốt hơn vì nó thậm chí còn ở mức độ thấp hơn." Đó là một điều xấu, không phải là một điều tốt.
Các cuộc đua nhẹ nhàng trong quỹ đạo

54

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

Đống

  • 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

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

Cây rơm : Trong các mục ngăn xếp, mọi thứ chồng lên 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! ...

Đống : 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 có thể 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:

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


49

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

Chồng

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 nó 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.

Nhiều hơn có thể được tìm thấy ở đâ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.

Nhiều hơn có thể được tìm thấy ở đâ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 vùng 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 vùng 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.

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

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 khi 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 heap cho các loại phân bổ khác nhau).

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

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í, chi tiết hơn được đưa ra ở đâyở đây .


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

Ở 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ữ?

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.

Nhiều hơn có thể được tìm thấy ở đâ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 lượng dữ liệu bạn cần phân bổ trước thời gian 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 ở đâ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 thiết lập bởi hệ điều hành 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 không gian (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ể.

Thông tin chi tiết có thể được tìm thấy từ đây .


36

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ự lăn lộn. 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 là 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 ra 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 heap có sẵn.

STACK duy nhất thường là một khu vực bên dưới HEAP, là một bộ nhớ chứa không có giá trị gì 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ếp trong một trong những vụ 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ị bằng 0), vô tình không được cung cấp trong một sản phẩm. Một cái khác là DATA chứa các giá trị khởi tạo, bao gồm cả chuỗi và số. Thứ ba là CODE chứa CRT (thời gian chạy C), chính, hàm 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 tại sao các khối này cần phải liền kề, hoặc cố định về kích thước, hoặc đặt hàng một cách cụ thể bây giờ. Tất nhiên, trước UNIX là Multics không gặp phải những hạn chế này. Dưới đây là một sơ đồ cho thấy một trong những bố trí bộ nhớ của thời đại đó.

Một bố cục bộ nhớ chương trình UNIX C điển hình của những năm 1980



26

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:

Đây là tầm nhìn của tôi về xây dựng bộ nhớ quá trình với sự đơn giản hóa để dễ hiểu hơn khi xảy ra


Mũi tên - hiển thị nơi phát triển stack stack và heap, kích thước stack stack 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ụ 32 bit 2-4 GB.

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 trả về hàm con trỏ 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ộ.


23

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 (tức 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 các sợi , luồng 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 ở đây .

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

Khi sử dụng sợi, chỉ xanh 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 thi là theo 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 các ngăn xếp là động và không được xác định bởi số lượng các luồng cấp hệ điều hành!

Lưu ý rằng tôi đã nói " thường có một ngăn xếp riêng cho mỗi chức năng". Như vậy là cả hai stackfulStackless triển khai của couroutines. Hầu hết các trường đáng chú ý stackful C ++ là Boost.CoroutineMicrosoft PPL 's async/await. (Tuy nhiên, các chức năng có thể nối lại của C ++ (còn gọi là " asyncawait"), được đề xuất cho C ++ 17, có khả năng sử dụng các coroutines stackless.)

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


19

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

Đống

  • 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ớ).

1
súc tích và sạch sẽ. tốt đẹp :)
ingconti

13

Ồ 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à ở đâu (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ế để hữu í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


8

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 cho 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ữ lắp ráp ..). (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 / giữ → 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 bậc thầy về vi xử lý sẽ nói hay một cuốn sách BIOS năm 1980 tốt ...)

  • Không có stack không có bộ vi xử lý có thể làm việc. (chúng ta không thể tưởng tượng một chương trình, ngay cả trong ngôn ngữ lắp ráp, 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ữ lắp ráp 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í đẩy / 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ổ.

OPP là gì? Ý bạn là OOP ( object-direction_programming )?
Peter Mortensen

Bạn có nghĩa là để nói rằng đó malloclà một cuộc gọi hạt nhân?
Peter Mortensen

1) có, xin lỗi .. OOP ... 2) malloc: Tôi viết ngắn gọn, xin lỗi ... malloc đang ở trong không gian người dùng .. nhưng có thể kích hoạt các cuộc gọi khác .... vấn đề là sử dụng heap có thể rất chậm ...
ingconti

" Rất nhiều câu trả lời là chính xác 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 cho phần cứng (tức là bộ vi xử lý) để cho phép gọi chương trình con (GỌI bằng ngôn ngữ lắp ráp ..) ". Bạn đang nhầm lẫn ngăn xếp CPU (nếu có một trong CPU hiện đại) và ngăn xếp thời gian chạy ngôn ngữ (một cho mỗi luồng). Khi các lập trình viên nói về một ngăn xếp, đây là ngăn xếp thực thi luồng của thời gian chạy, ví dụ như ngăn xếp luồng NET), chúng ta không nói về ngăn xếp CPU.
phút

1

Ngăn xếp về cơ bản là một bộ nhớ dễ truy cập, chỉ đơn giản là quản lý các mục của nó dưới dạng ngăn xếp - tốt. Chỉ các mục mà kích thước được biết trước có thể đi lên ngăn xếp . Đây là trường hợp cho số, chuỗi, booleans.

Các đống là một bộ nhớ cho các hạng mục mà bạn không thể định trước kích thước và cấu trúc chính xác . Vì các đối tượng và mảng có thể bị thay đổi và thay đổi khi chạy, chúng phải đi vào heap.

Nguồn: Học giả


0

Cảm ơn bạn cho một cuộc thảo luận thực sự tốt nhưng như một người mới thực sự tôi tự hỏi hướng dẫn được giữ ở đâu? Trong BEGINNING, các nhà khoa học đã quyết định giữa hai kiến ​​trúc (von NEUMANN, nơi mọi thứ được coi là DATA và HARVARD nơi một vùng bộ nhớ được dành cho hướng dẫn và một vùng khác cho dữ liệu). Cuối cùng, chúng tôi đã đi với thiết kế von Neumann và bây giờ mọi thứ được coi là "giống nhau". Điều này gây khó khăn cho tôi khi tôi học lắp ráp https://www.cs.virginia.edu/~evans/cs216/guides/x86.html vì họ nói về các thanh ghi và ngăn xếp con trỏ.

Tất cả mọi thứ ở trên nói về DATA. Tôi đoán là vì một lệnh là một thứ được xác định với dấu chân bộ nhớ cụ thể, nên nó sẽ đi vào ngăn xếp và vì vậy tất cả các 'thanh ghi' được thảo luận trong lắp ráp đều nằm trên ngăn xếp. Tất nhiên sau đó đã đến lập trình hướng đối tượng với các hướng dẫn và dữ liệu được đưa vào một cấu trúc động, vì vậy bây giờ các hướng dẫn cũng sẽ được giữ trong heap?

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.