Câu hỏi Những gì và ở đâu là ngăn xếp và đống?


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 cây rơmvà các loại tham chiếu được tạo trên đống, mà không giải thích hai điều 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 những gì một chồng Là. Nhưng,

  • ở đâu và chúng là gì (vật lý trong bộ nhớ của máy tính thực)?
  • Chúng được kiểm soát 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 nhanh hơn?

7126
2017-09-17 04:18


gốc


một lời giải thích thực sự tốt có thể được tìm thấy ở đây Sự khác nhau giữa ngăn xếp và vùng heap là gì? - Songo
Ngoài ra (thực sự) tốt: codeproject.com/Articles/76153/… (phần stack / heap) - Ben
Giải thích tốt có thể được tìm thấy đây - Bharat
youtube.com/watch?v=clOUdVDDzIM&spfreload=5 - Selvamani
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 hệ thống và các hành vi như rlimit_stack. Xem thêm Red Hat Số phát hành 1463241 - jww


Các câu trả lời:


Ngăn xếp là bộ nhớ được đặt sang một bên là không gian đầu cho một luồng thực hiện. Khi một hàm được gọi, một khối được đặt trước trên cùng của ngăn xếp cho các biến cục bộ và một số dữ liệu sổ 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 sau khi một hàm được gọi. Ngăn xếp luôn được bảo lưu trong một lệnh LIFO (cuối cùng trong lần ra trước); khối được 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 các ngăn xếp; giải phóng một khối từ ngăn xếp là không có gì nhiều hơn đ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ẫu được thực thi nào cho việc phân bổ và deallocation của các khối từ heap; 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 những phần nào của vùng được cấp phát hoặc miễn phí tại bất kỳ thời điểm nào; có nhiều trình phân bổ heap tùy chỉnh có sẵn để điều chỉnh hiệu suất heap cho các mẫu sử dụng khác nhau.

Mỗi luồng có một chồng, trong khi thường chỉ có một đống cho ứng dụng (mặc dù nó không phải là hiếm khi có nhiều đống cho các kiểu phân bổ khác nhau).

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

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

Hệ điều hành phân bổ stack cho mỗi thread cấp hệ thống khi thread được tạo ra. Thông thường, hệ điều hành được gọi bởi thời gian chạy 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ới một chủ đề, vì vậy khi thoát khỏi ngăn xếp được yêu cầu. Heap thường được cấp phát khi khởi động ứng dụng theo thời gian chạy và được khai hoang 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 tạo chuỗi. Kích thước của heap được thiết lập khi khởi động ứng dụng, nhưng có thể phát triển như không gian là cần thiết (bộ cấp phát yêu cầu nhiều bộ nhớ hơn từ hệ điều hành).

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

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

Một cuộc biểu tình rõ ràng:
Nguồn hình ảnh: vikashazrati.wordpress.com


5239
2017-09-17 04:52



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ệ điều hành khi quá trình bắt đầu (giả sử sự tồn tại của một hệ điều hành), nó được duy trì nội tuyến bởi chương trình. Đây là một lý do khác khiến stack nhanh hơn, cũng như các hoạt động push và pop thường là một lệnh máy, và các máy hiện đại có thể làm ít nhất 3 trong số đó trong một chu kỳ, trong khi phân bổ hoặc giải phóng heap liên quan đến việc gọi vào mã OS. - sqykly
Tôi thực sự bối rối bởi sơ đồ ở cuối. Tôi nghĩ tôi đã nhận được nó cho đến khi tôi nhìn thấy hình ảnh đó. - Sina Madani
@Anarelle bộ vi xử lý chạy các hướng dẫn có hoặc không có một os. Một ví dụ gần gũi với trái tim tôi là SNES, mà không có cuộc gọi API, không có hệ điều hành 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à cộng và trừ trên các hệ thống này và điều đó là 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 constrast rằng, nói, một nhà xây dựng, trong đó kết quả không thể chỉ vứt đi. Cho rằng chúng ta cần đống, mà không phải là gắn liền với cuộc gọi và trả lại. Hầu hết các hệ điều hành có API một đống, không có lý do để làm điều đó một mình - sqykly
"stack là bộ nhớ đặt sang một bên là không gian đầu". Mát mẻ. Nhưng đâu là nó thực sự "đặt sang một bên" về cấu trúc bộ nhớ Java ?? Có bộ nhớ Heap / bộ nhớ không phải bộ nhớ heap / khác (cấu trúc bộ nhớ Java theo betsol.com/2017/06/… ) - Jatin Shashoo


Cây rơm:

  • Được lưu trữ trong RAM máy tính giống như heap.
  • Các biến được tạo trên ngăn xếp sẽ nằm ngoài phạm vi và được tự động phân bổ.
  • Phân bổ nhanh hơn nhiều so với các biến trên heap.
  • Được thực hiện với cấu trúc dữ liệu chồng thực tế.
  • Lưu trữ dữ liệu cục bộ, trả về địa chỉ, được sử dụng để truyền tham số.
  • Có thể có một ngăn xếp tràn khi quá nhiều ngăn xếp được sử dụng (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ó con trỏ.
  • Bạn sẽ sử dụng ngăn xếp nếu bạn biết chính xác có bao nhiêu 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.
  • Thông 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ỏ thủ công và không bao giờ rơi ra khỏi phạm vi. Dữ liệu được giải phóng với delete, delete[], hoặc là 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 để chương trình sử dụng.
  • Có thể có phân mảnh khi có rất nhiều phân bổ và deallocations.
  • Trong C ++ hoặc C, dữ liệu được tạo trên heap sẽ được trỏ tới bởi con trỏ và được phân bổ bằng new hoặc là malloc tương ứng.
  • Có thể có lỗi phân bổ nếu quá lớn của bộ đệm được yêu cầu được cấp phát.
  • Bạn sẽ sử dụng heap nếu bạn không biết chính xác có bao nhiêu dữ liệu bạn sẽ cần trong thời gian chạy hoặc nếu bạn cần phân bổ rất 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;

2095
2017-09-17 04:20



Con trỏ pBuffer và giá trị của b được đặt trên stack, và chủ yếu là 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 cấp phát tại lối vào chức năng. - Andy
Đó là một quan niệm sai lầm phổ biến rằng C ngôn ngữ, như được định nghĩa bởi C99 chuẩn ngôn ngữ (có sẵn tại open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), yêu cầu "ngăn xếp". Trong 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 wrt / to CViệc sử dụng ngăn xếp là sự thật nói chung, nhưng không có cách nào theo yêu cầu của ngôn ngữ. Xem knosof.co.uk/cbook/cbook.html để biết thêm thông tin và đặc biệt cách C được triển khai trên các kiến ​​trúc bóng lẻ như en.wikipedia.org/wiki/Burroughs_large_systems - johne
@Brian Bạn nên giải thích tại sao buffer [] và con trỏ pBuffer được tạo trên stack 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 là cụ thể hướng dẫn rằng bộ nhớ được phân bổ trên stack vs heap nhưng điều này không phải là trường hợp. Có phải vì Buffer là một kiểu giá trị trong khi pBuffer là một kiểu tham chiếu? - Howiecamp
@Remover: Không có con trỏ nào giữ địa chỉ và nó có thể trỏ đến thứ gì đó trên đống hoặc ngăn xếp bằng nhau. new, malloc và một số hàm khác tương tự như malloc phân bổ trên heap và trả về địa chỉ của bộ nhớ đã được cấp phát. 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 đi ra khỏi phạm vi và được phát hành cho đến khi bạn muốn nó. - Brian R. Bondy
"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ớ! Lazy / Quên / ex-java coders / coders những người không cung cấp cho một crap được! - Laz


Điểm quan trọng nhất là đống và ngăn xếp là các thuật ngữ chung cho các cách thức mà bộ nhớ có thể được cấp phát. 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 mục, các mục nằm trên đầu trang của các khác theo thứ tự chúng được đặt ở đó, và bạn chỉ có thể loại bỏ một trong những đầu (mà không lật đổ toàn bộ điều trên).

    Stack like a stack of papers

    Sự đơn giản của một ngăn xếp là bạn không cần phải duy trì một bảng có chứa một bản ghi của mỗi phần của bộ nhớ được cấp phát; 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. Để cấp phát và phân bổ, bạn chỉ cần tăng và giảm con trỏ đơn. Lưu ý: một ngăn xếp đôi khi có thể được thực hiện để bắt đầu ở đầu phần của bộ nhớ và mở rộng xuống dưới chứ không phải là tăng lên.

  • Trong một đống, không có thứ tự cụ thể nào cho các mục được đặt. 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 'hàng đầu' rõ ràng nào.

    Heap like a heap of licorice allsorts

    Phân bổ đống yêu cầu duy trì một bản ghi đầy đủ về bộ nhớ được cấp phát và những gì không, cũng như bảo trì trên không để giảm phân mảnh, tìm các phân đoạn bộ nhớ tiếp giáp đủ lớn để phù hợp với kích thước yêu cầu, v.v. Bộ nhớ có thể được deallocated bất cứ lúc nào để lại không gian miễn phí. Đôi khi một 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 khi chạy khi bộ nhớ không còn trong phạm vi và xử lý nó.

Những hình ảnh này nên làm một công việc khá tốt của mô tả hai cách phân bổ và giải phóng bộ nhớ trong một ngăn xếp và một đống. Yum!

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

    Như đã đề cập, đống và ngăn xếp là các thuật ngữ chung và có thể được triển khai theo nhiều cách. Các chương trình máy tính thường có một chồng đượ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ư con trỏ đến bất kỳ hàm nào được gọi từ đó và bất kỳ biến cục bộ nào. Bởi vì chức năng gọi các chức năng khác và sau đó trở lại, ngăn xếp phát triển và co lại để giữ thông tin từ các chức năng 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.

    Một đống là một thuật ngữ chung được sử dụng cho bất kỳ bộ nhớ nào được cấp phát động và ngẫu nhiên; tức là không đúng thứ tự. Bộ nhớ thường được phân bổ bởi hệ điều hành, với các ứng dụng gọi API chức năng để làm phân bổ này. Có một chút công bằng của 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 hệ điều hành.

  • 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 như vậy mà 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 với các phần của ngăn xếp, nhưng theo như một ngôn ngữ cấp cao hơn là có liên quan, ngôn ngữ áp đặt các quy tắc phạm vi riêng của nó. Tuy nhiên, một khía cạnh quan trọng của một ngăn xếp là khi một hàm trả về, bất kỳ thứ gì cục bộ cho hàm đó đượ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 được cách ngôn ngữ lập trình của bạn hoạt động như thế nào. Trong một đống, nó cũng khó xác định. Phạm vi là bất cứ điều gì được tiếp xúc bởi hệ điều hành, nhưng ngôn ngữ lập trình của bạn có thể thêm quy tắc của nó về những gì một "phạm vi" là trong ứng dụng của bạn. Kiến trúc bộ vi xử lý và hệ điều hành sử dụng địa chỉ ảo, bộ vi xử lý dịch sang địa chỉ vật lý và có lỗi trang, vv Chúng theo dõi những trang thuộc về ứng dụng nào. Bạn không bao giờ thực sự cần phải lo lắng về điều này, bởi vì bạn chỉ cần sử dụng bất kỳ phương pháp ngôn ngữ lập trình của bạn sử dụng để cấp phát và bộ nhớ miễn phí, và kiểm tra lỗi (nếu phân bổ / giải phóng không thành công vì 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ớ tiếp giáp (nhiều hơn về điều đó trong đoạn cuối). Trình biên dịch ngôn ngữ hoặc hệ điều hành xác định kích thước của nó. Bạn không lưu trữ các 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ừ 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ộ vi xử lý và hệ điều hành hiện đại, cách thức hoạt động chính xác là rất trừu tượng, vì vậy bạn thường không cần phải lo lắng về cách nó hoạt động sâu, ngoại trừ (bằng ngôn ngữ cho phép bạn), bạn không được sử dụng bộ nhớ bạn chưa phân bổ hoặc bộ nhớ mà bạn đã giải phóng.

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

    Ngăn xếp nhanh hơn vì tất cả bộ nhớ miễn phí luôn tiếp giáp. Không có danh sách nào cần phải được duy trì của tất cả các phân đoạn của bộ nhớ miễn phí, 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 đặc biệt, nhanh chóng ghi danh vì mục đích này. Hơn nữa, các hoạt động tiếp theo trên một chồng thường tập trung trong các khu vực rất gần của bộ nhớ, ở mức rất thấp là tốt cho tối ưu hóa bởi bộ xử lý trên bộ nhớ cache.


1261
2018-03-19 14:38



David Tôi không đồng ý rằng đó là một hình ảnh tốt hay "ngăn xếp đẩy xuống" là một thuật ngữ tốt để minh họa cho khái niệm. Khi bạn thêm thứ gì đó vào ngăn xếp, các nội dung khác của ngăn xếp không phải bị đẩy xuống, họ vẫn ở đâu. - thomasrutter
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 cấp phát 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 cân bằng các biến "tự động" với biến "tĩnh", nhưng chúng không giống nhau - davec
Cụ thể, bạn nói "các biến cục bộ được phân bổ tĩnh" được cấp phát trên ngăn xếp. Trên thực tế chúng được phân bổ trong phân đoạn 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à những thứ như tham số hàm được truyền theo giá trị thay vì tham chiếu) được cấp phát trên ngăn xếp. - davec
Tôi vừa mới nhận ra bạn đúng - trong C, phân bổ tĩnh là điều riêng biệt của riêng nó chứ không phải là thuật ngữ cho bất kỳ thứ gì không năng động. Tôi đã chỉnh sửa câu trả lời của tôi, cảm ơn. - thomasrutter
Không chỉ C. Java, Pascal, Python và nhiều thứ khác đều có khái niệm phân bổ tĩnh so với tự động so với phân bổ động. Nói "phân bổ tĩnh" có nghĩa là giống nhau ở 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ổ cụm từ "tự động" cho những gì bạn mô tả (nghĩa là những thứ trên ngăn xếp). - davec


(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 sự lừa dối của câu hỏi này.)

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

  • Cả stack và heap là vùng nhớ được cấp phát 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ôi trường đa luồng, mỗi luồng sẽ có ngăn xếp hoàn toàn độc lập nhưng chúng sẽ chia sẻ đống. Truy cập đồng thời phải được kiểm soát trên heap và không thể thực hiện trên stack.

Đống

  • Heap chứa một danh sách liên kết 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 là malloc) hài lòng 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 đống phát triển khối mới thường được phân bổ từ địa chỉ thấp hơn đối với địa chỉ cao hơn. Vì vậy, bạn có thể nghĩ về heap như một đống các khối bộ nhớ tăng kích thước khi bộ nhớ được cấp phát. Nếu heap là quá nhỏ cho một phân bổ kích thước thường có thể được tăng lên bằng cách mua thêm bộ nhớ từ hệ điều hành cơ bản.
  • Phân bổ và deallocating nhiều khối nhỏ có thể để lại đống trong một tiểu bang nơi có rất nhiều khối nhỏ miễn phí 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í đủ 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 đống.
  • Khi một khối được sử dụng liền kề với một khối miễn phí được deallocated khối miễn phí mới có thể được sáp nhập với khối miễn phí liền kề để tạo ra một khối miễn phí lớn hơn có hiệu quả làm giảm sự phân mảnh của đống.

The heap

Ngăn xếp

  • 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 trỏ lên đầu 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ị lên ngăn xếp và popping chúng trở lại từ ngăn xếp. Mỗi đẩy lưu trữ 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ỏ tới 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 vào một giá trị cho 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 ở phía dưới). Các giá trị được lưu trữ và truy xuất là các giá trị của thanh ghi CPU.
  • Khi một hàm được gọi là CPU sử dụng các lệnh đặc biệt để đẩy dòng điện con trỏ chỉ dẫn, 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 thiết lập hướng dẫn con trỏ đến đị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 sẽ tiếp tục lại tại mã ngay sau khi gọi hàm.
  • Khi một hàm được nhập vào, con trỏ ngăn xếp được giảm xuống để phân bổ thêm không gian 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 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 lên ngăn xếp từ con trỏ ngăn xếp hiện tại để định vị các giá trị này.
  • Các cuộc gọi hàm lồng nhau hoạt động 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à hồ sơ 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ả về.
  • 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ì 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ể bị bắt bởi thời gian chạy và chuyển đổi thành một số loại ngoại lệ tràn ngăn xếp.

The stack

Một hàm có thể được cấp phát trên heap thay vì ngăn xếp không?

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

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

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ộ vi xử lý. Phát triển vùng heap khi không đủ không gian không quá khó vì nó có thể được thực hiện trong cuộc gọi thư viện xử lý đống. Tuy nhiên, việc phát triển ngăn xếp thường không thể vì ngăn xếp ngăn xếp 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.


664
2017-07-31 15:54



@Martin - Một câu trả lời / giải thích rất tốt so với 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ị con trỏ ngăn xếp / sổ đăng ký đang được sử dụng cho một cuộc gọi chức năng vis sẽ minh họa hơn. - Bikal Lem
Mỗi loại tham chiếu là thành phần của các kiểu giá trị (int, string, v.v.). Như đã nói, các loại giá trị đó được lưu trữ trong ngăn xếp so với loại giá trị hoạt động khi chúng là một phần của loại tham chiếu. - Nps
Câu trả lời này là tốt nhất theo ý kiến ​​của tôi, bởi vì nó giúp tôi hiểu câu trả lời thực sự là gì và nó liên quan đến "địa chỉ trả lại" mà tôi gặp phải như thế nào bây giờ, và tại sao chức năng được đẩy lên ngăn xếp. Câu trả lời chính xác! - Alex
Điều này là tốt nhất theo ý kiến ​​của tôi, cụ thể là để đề cập đến heap / stack là rất thực hiện cụ thể. Các câu trả lời khác giả định nhiều về những thứ về ngôn ngữ và môi trường / hệ điều hành. +1 - Qix
Những gì bạn có nghĩa là "Mã trong chức năng sau đó có thể điều hướng lên ngăn xếp từ con trỏ ngăn xếp hiện tại để xác định vị trí các giá trị này." ? Bạn có thể giải thích về điều này không? - Koray Tugay


Trong mã C # sau

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

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

Picture of variables on the stack

Local Variables mà chỉ cần kéo dài miễn là lời gọi hàm xuất hiện 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 tôi mong đợi chúng 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 gian biên dịch một biến lớn như thế 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 trên heap bởi vì chúng tôi không biết tại thời gian tạo ra bao lâu họ sẽ kéo dài. Trong nhiều ngôn ngữ, đống rác được thu thập để tìm các đối tượng (chẳng hạn như đối tượng cls1) mà không còn 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ấu trúc và lớp học thường có thể vẫn còn trên stack khi bạn không giao dịch với con trỏ.

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

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

và đây:

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

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

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


352
2017-11-09 12:28



Đ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ự khác biệt rất quan trọng. Xem [link] stackoverflow.com/a/13326916/1763801 để làm rõ - davec
Tôi không nói là họ tĩnh biến. Tôi nói rằng int và cls1 là tĩnh mặt hàng. Bộ nhớ của họ được phân bổ tĩnh và do đó họ đi vào ngăn xếp. Điều này trái ngược với một đối tượng yêu cầu phân bổ bộ nhớ động, do đó nó sẽ đi vào heap. - Snowcrash
Tôi trích dẫn "các mục tĩnh ... đi trên ngăn xếp". Điều này chỉ là phẳng ra sai. Các mục tĩnh đi trong phân đoạn dữ liệu, các mục tự động sẽ nằm trên ngăn xếp. - davec
Ngoài ra bất cứ ai đã viết bài viết mã hóa đó không biết anh ta đang nói về cái gì. Ví dụ, ông nói "những người nguyên thuỷ cần bộ nhớ kiểu tĩnh", điều này hoàn toàn không đúng sự thật. Không có gì ngăn bạn phân bổ nguyên thủy trong heap động, chỉ cần viết một cái gì đó như "int array [] = new int [num]" và thì đấy, nguyên thủy được phân bổ động trong .NET. Đó chỉ là một trong vài điểm không chính xác. - davec
Tôi đã chỉnh sửa bài đăng của bạn vì bạn đã mắc phải những sai lầm kỹ thuật nghiêm trọng về những gì xảy ra trong ngăn xếp và đống. - Tom Leys


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ố phí khác được đặt trên ngăn xếp. Một số thông tin (chẳng hạn như nơi để đi trên trở lại) 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 cấp phát trên ngăn xếp.

Deallocating ngăn xếp là khá đơn giản bởi vì bạn luôn deallocate theo thứ tự ngược lại, trong đó bạn phân bổ. Công cụ ngăn xếp được thêm khi bạn nhập các hàm, 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 rất 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 ra khi đang di chuyển. Nếu bạn không biết có bao nhiêu phi thuyền không gian chương trình của bạn sẽ tạo ra, bạn có thể sử dụng toán tử mới (hoặc malloc hoặc tương đương) để tạo ra mỗi phi thuyền. Việc phân bổ này sẽ được thực hiện 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 thứ tự khác với chúng tôi đã tạo ra chúng.

Do đó, heap phức tạp hơn rất nhiều, bởi vì cuối cùng là các 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 các đống nên tránh (mặc dù nó vẫn còn được sử dụng).

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

Điều này chỉ thực tế nếu 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 rất lớn và có thể mút toàn bộ rất nhiều trong một hoạt động khổng lồ khác.

Vị trí thực tế trong bộ nhớ Điều này ít liên quan hơn bạn nghĩ vì một công nghệ được gọi là Bộ nhớ ảo làm cho 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ý ở đâu đó khác (ngay cả trên đĩa cứng!). Các địa chỉ bạn nhận được cho ngăn xếp đang tăng dần khi cây gọi của bạn trở nên sâu hơn. Các địa chỉ cho heap là không thể dự đoán (nghĩa là implimentation cụ thể) và thẳng thắn không quan trọng.


191
2017-09-17 04:27



Một khuyến nghị để tránh sử dụng đống là khá mạnh. Các hệ thống hiện đại có trình quản lý heap tốt, và các ngôn ngữ động hiện đại sử dụng đống rộng rãi (không có lập trình viên thực sự đáng lo ngại về nó). Tôi muốn nói sử dụng heap, nhưng với một cấp phát bằng tay, đừng quên miễn phí! - Greg Hewgill
Nếu bạn có thể sử dụng ngăn xếp hoặc đống, hãy 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ó sự lựa chọn. Tôi sử dụng cả hai rất nhiều, và tất nhiên bằng cách sử dụng std :: vector hoặc tương tự hit heap. Đối với một người mới, bạn tránh đống bởi vì ngăn xếp chỉ đơn giản là dễ dàng như vậy !! - Tom Leys
Nếu ngôn ngữ của bạn không thực hiện thu gom rác, con trỏ thông minh (các đối tượng được phân bổ theo thứ tự bao quanh một con trỏ để tính số lượng bộ nhớ được phân bổ động) liên quan chặt chẽ đến việc thu gom rác và là cách quản lý heap an toàn và bị rò rỉ miễn phí. Chúng được thực hiện trong nhiều khung công tác 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
"Đây là lý do tại sao các đống nên tránh (mặc dù nó vẫn còn được sử dụng)." Tôi không chắc điều này có ý nghĩa gì, đặc biệt khi bộ nhớ được quản lý khác nhau ở nhiều ngôn ngữ cấp cao. Vì câu hỏi này được gắn thẻ ngôn ngữ thuyết bất khả tri, tôi muốn nói chú thích / dòng cụ thể này là không đúng chỗ và không áp dụng được. - JonnoHampson
Điểm tốt @ JonnoHampson - Trong khi bạn thực hiện một điểm hợp lệ, tôi cho rằng nếu bạn đang làm việc trong một "ngôn ngữ cấp cao" với GC, bạn có thể không quan tâm đến cơ chế cấp phát bộ nhớ. thậm chí quan tâm đến đống và đống. - Tom Leys


Làm rõ, câu trả lời này có thông tin không chính xác (thomas cố định câu trả lời của mình sau khi bình luận, bình tĩnh :)). Câu trả lời khác chỉ cần tránh giải thích những gì phân bổ tĩnh có nghĩa là. 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 phân đoạn heap, ngăn xếp và 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 cấp phát tĩnh) không được cấp phát trên ngăn xếp. Đừng cho rằng như vậy - nhiều người chỉ làm vì "tĩnh" nghe rất giống "chồng". Họ thực sự tồn tại trong cả đống và cả đống. Là một phần của những gì đượ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 "phạm vi"và"cả đời"thay vì" ngăn xếp "và" đống ".

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

Tuổi thọ đề cập đến khi một biến được phân bổ và deallocated 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 gian 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 trên một số lệnh gọi hàm) so với phân bổ tự động (biến chỉ tồn tại trong một cuộc gọi duy nhất đến 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ỏ 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 biên dịch thực hiện hành vi này tương tự như trong việc sử dụng ngăn xếp, đống, vv, 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ị loại bỏ 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 nhận xét, bạn có thể tự do triển khai trình biên dịch thậm chí không sử dụng ngăn xếp hoặc đố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à heaps là tuyệt vời cho việc này).

Tôi sẽ cung cấp một số mã C có chú giải đơn giản để minh họa tất cả điều này. Cách tốt nhất để tìm hiểu là chạy chương trình dưới 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 là 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 chung 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 "địa phương"chúng tôi thường có nghĩa là"biến được phân bổ cục bộ tự động"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 phân tán 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 nghĩ rằng các biến toàn cầu không phải là "tĩnh" vì 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 ngăn không cho var2 có phạm vi toàn cầu. Tuy nhiên, var1 toàn cầu có phân bổ tĩnh. Đây không phải là 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 điều gì đó như phạm vi "tệp" hoặc "tệp giới hạn". 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 bối cảnh của cuộc đờ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à deallocated khi thoát chương trình.

Một số người nghĩ về những khái niệm này là C / C ++ cụ thể. Họ không phải. Ví dụ, mẫu Python dưới đây minh họa tất cả ba loại phân bổ (có một số khác biệt nhỏ có thể có trong các ngôn ngữ thông dị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

169
2017-09-17 04:48



Tôi sẽ tham chiếu đến một biến tĩnh được khai báo trong một hàm vì chỉ có địa phương khả năng tiếp cận, nhưng nói chung 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 ngăn xếp / heap với ngôn ngữ cơ bản không linh hoạ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 một ngăn xếp đó để giữ những thứ cần phác thảo ngữ cảnh trong đó chúng được tạo . 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 chồng. - supercat
@supercat Điều đó tất cả đều có ý nghĩa. Tôi đã xác định phạm vi là "những phần nào của mã có thể truy cập một biến "(và cảm thấy đây là định nghĩa tiêu chuẩn nhất) vì vậy tôi nghĩ rằng chúng tôi đồng ý :) - davec
Tôi sẽ coi "phạm vi" của một biến như 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 là bắt buộc để giữ giá trị của nó miễn là đối tượng tồn tại. Một biến trong phạm vi ngữ 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 ngữ cảnh đó. Một khai báo biến tĩnh tạo ra một định danh có phạm vi được giới hạn trong khối hiện tại, được gắn với biến có phạm vi không bị chặn. - supercat
@supercat Đây là lý do tại sao tôi sử dụng từ cuộc đời, đó là cách tôi gọi những gì bạn gọi là phạm vi thời gian. Nó làm giảm sự cần thiết phải quá tải từ "phạm vi" với rất nhiều ý nghĩa. Theo như tôi có thể nói, có vẻ như không có tổng số đồng thuận về định nghĩa chính xác mặc dù, ngay cả trong số các nguồn kinh điển. 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 mà tôi đã học / dạy tại. Luôn luôn tốt để nghe một cái nhìn thông báo khác. - davec
bạn đang đùa chắc. bạn có thể xác định biến tĩnh bên trong một hàm không? - Zaeem Sattar


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

  1. Stack và đống không cần phải là số ít. Một tình huống phổ biến mà 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. Bạn cũng có thể có nhiều hơn một đống, ví dụ một số cấu hình DLL có thể dẫn đến các DLL khác nhau phân bổ từ các đống khác nhau, đó là lý do tại sao nó thường là một ý tưởng tồi để giải phóng bộ nhớ được cấp phát 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 vùng heap. Bộ nhớ này sẽ không tồn tại được câu lệnh trả về của bạn, nhưng nó rất hữu ích cho một 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 phần lớn là không 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 đầu dò stack được gọi mỗi lần hàm của bạn được nhập vào để đả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 cần phát triển ngăn xếp. Nếu bạn truy cập vào 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.
}

156
2017-09-17 07:16



Re "như trái ngược với phân bổ": Bạn có nghĩa là "trái ngược với malloc"? - Peter Mortensen
Làm thế nào cầm tay là alloca? - Peter Mortensen
@PeterMortensen nó không phải POSIX, tính di động không được bảo đảm. - Don Neufeld