Là ngăn xếp là cách hợp lý duy nhất để cấu trúc chương trình?


74

Hầu hết các kiến ​​trúc tôi từng thấy đều dựa vào ngăn xếp cuộc gọi để lưu / khôi phục ngữ cảnh trước khi gọi hàm. Đó là một mô hình phổ biến rằng các hoạt động đẩy và pop được tích hợp cho hầu hết các bộ xử lý. Có hệ thống nào hoạt động mà không có ngăn xếp? Nếu vậy, làm thế nào để họ làm việc, và họ được sử dụng để làm gì?


5
Dựa vào cách các chức năng được mong đợi hoạt động trong các ngôn ngữ giống như C (nghĩa là bạn có thể lồng các cuộc gọi sâu như bạn muốn và có thể quay lại theo thứ tự ngược lại), tôi không rõ người khác có thể thực hiện các cuộc gọi chức năng như thế nào mà không thể tin được không hiệu quả. Ví dụ, bạn có thể buộc lập trình viên sử dụng kiểu chuyển tiếp hoặc một số hình thức lập trình kỳ quái khác, nhưng dường như không ai thực sự làm việc với CPS ở mức độ thấp vì một số lý do.
Kevin

5
GLSL hoạt động mà không có ngăn xếp (cũng như các ngôn ngữ khác trong khung cụ thể đó). Nó chỉ đơn giản là không cho phép các cuộc gọi đệ quy.
Leushenko

3
Bạn cũng có thể muốn xem xét các cửa sổ Đăng ký , được sử dụng bởi một số kiến ​​trúc RISC.
Đánh dấu gian hàng

2
@Kevin: "Các trình biên dịch FORTRAN sớm không hỗ trợ đệ quy trong chương trình con. Các kiến ​​trúc máy tính ban đầu không hỗ trợ khái niệm ngăn xếp và khi chúng hỗ trợ trực tiếp các lệnh gọi chương trình con, vị trí trả về thường được lưu trữ ở một vị trí cố định bên cạnh mã chương trình con. không cho phép một chương trình con được gọi lại trước khi cuộc gọi trước của chương trình con trở lại. Mặc dù không được chỉ định trong Fortran 77, nhiều trình biên dịch F77 đã hỗ trợ đệ quy như một tùy chọn, trong khi nó trở thành một tiêu chuẩn trong Fortran 90. " vi.wikipedia.org/wiki/Fortran#FORTRAN_II
Vịt Mooing

3
Bộ vi điều khiển P8X32A ("Cánh quạt") không có khái niệm về ngăn xếp trong ngôn ngữ lắp ráp tiêu chuẩn (PASM). Các hướng dẫn chịu trách nhiệm nhảy cũng tự sửa đổi hướng dẫn trở lại trong RAM để xác định nơi quay lại - có thể được chọn tùy ý. Thật thú vị, ngôn ngữ "Spin" (một ngôn ngữ cấp cao được giải thích chạy trên cùng một con chip) có ngữ nghĩa ngăn xếp truyền thống.
Wossname

Câu trả lời:


50

Một thay thế (phần nào) phổ biến cho ngăn xếp cuộc gọi là sự tiếp nối .

Ví dụ, VM Parrot dựa trên tiếp tục. Nó hoàn toàn không có chồng: dữ liệu được lưu giữ trong các thanh ghi (như Dalvik hoặc LuaVM, Parrot dựa trên thanh ghi) và luồng điều khiển được biểu diễn bằng các phần tiếp theo (không giống như Dalvik hoặc LuaVM, có ngăn xếp cuộc gọi).

Một cấu trúc dữ liệu phổ biến khác, thường được sử dụng bởi Smalltalk và Lisp VM là ngăn xếp spaghetti, giống như một mạng lưới các ngăn xếp.

Như @rwong đã chỉ ra , kiểu chuyển tiếp tiếp tục là một thay thế cho ngăn xếp cuộc gọi. Các chương trình được viết theo (hoặc chuyển đổi thành) kiểu truyền tiếp tục không bao giờ quay trở lại, do đó không cần phải xếp chồng.

Trả lời câu hỏi của bạn từ một góc độ khác: có thể có một ngăn xếp cuộc gọi mà không có một ngăn xếp riêng biệt, bằng cách phân bổ các khung ngăn xếp trên heap. Một số triển khai Lisp và Scheme làm điều này.


4
Nó phụ thuộc vào định nghĩa của bạn về một ngăn xếp. Tôi không chắc chắn rằng việc có một danh sách được liên kết (hoặc mảng con trỏ tới hoặc ...) của các khung ngăn xếp là rất nhiều "không phải là một ngăn xếp" như "một cách biểu diễn khác của ngăn xếp"; và các chương trình trong các ngôn ngữ CPS (trong thực tế) có xu hướng xây dựng các danh sách liên tục được liên kết hiệu quả rất giống với các ngăn xếp (nếu bạn không có, bạn có thể kiểm tra GHC, nó đẩy cái mà nó gọi là "tiếp tục" lên một ngăn xếp tuyến tính cho hiệu quả).
Jonathan Cast

6
" Các chương trình được viết theo (hoặc chuyển đổi thành) phong cách tiếp tục không bao giờ quay trở lại " ... nghe có vẻ đáng ngại.
Rob Penridge

5
@RobPenridge: tôi hơi khó hiểu, tôi đồng ý. CPS có nghĩa là thay vì trả về, các hàm lấy làm đối số phụ để gọi khi công việc của chúng được thực hiện. Vì vậy, khi bạn gọi một chức năng và bạn có một số công việc khác bạn cần thực hiện sau khi bạn đã gọi chức năng đó, thay vì chờ chức năng quay trở lại và sau đó tiếp tục với công việc của bạn, bạn tiếp tục công việc còn lại ("tiếp tục" ) vào một hàm và truyền hàm đó dưới dạng một đối số bổ sung. Hàm bạn đã gọi sau đó gọi hàm đó thay vì trả về, v.v. Không có chức năng nào trở lại, nó chỉ
Jörg W Mittag

3
Sàng gọi hàm tiếp theo. Do đó, bạn không cần ngăn xếp cuộc gọi, vì bạn không bao giờ cần quay lại và khôi phục trạng thái ràng buộc của hàm được gọi trước đó. Thay vì mang theo trạng thái quá khứ để bạn có thể quay lại trạng thái đó, bạn sẽ mang theo trạng thái tương lai , nếu bạn muốn.
Jörg W Mittag

1
@jcast: Tính năng xác định của ngăn xếp là IMO mà bạn chỉ có thể truy cập phần tử trên cùng. Một danh sách các phần tiếp theo, OTOH, sẽ cung cấp cho bạn quyền truy cập vào tất cả các phần tiếp theo và không chỉ là stackframe trên cùng. Ví dụ, nếu bạn có các ngoại lệ có thể tiếp tục theo kiểu Smalltalk, bạn cần có khả năng duyệt qua ngăn xếp mà không cần bật nó. Và việc tiếp tục sử dụng ngôn ngữ trong khi vẫn muốn giữ ý tưởng quen thuộc về ngăn xếp cuộc gọi dẫn đến ngăn xếp spaghetti, về cơ bản là một cây ngăn xếp nơi tiếp tục "rẽ nhánh" ngăn xếp.
Jörg W Mittag

36

Vào thời xa xưa, các bộ xử lý không có hướng dẫn ngăn xếp và các ngôn ngữ lập trình không hỗ trợ đệ quy. Theo thời gian, ngày càng có nhiều ngôn ngữ chọn hỗ trợ đệ quy và bộ phần cứng theo sau với khả năng phân bổ khung ngăn xếp. Hỗ trợ này đã thay đổi rất nhiều trong những năm qua với các bộ xử lý khác nhau. Một số bộ xử lý đã thông qua khung ngăn xếp và / hoặc các thanh ghi con trỏ ngăn xếp; một số hướng dẫn được thông qua sẽ thực hiện việc phân bổ các khung ngăn xếp trong một lệnh duy nhất.

Khi các bộ xử lý tiến bộ với cấp độ đơn, sau đó là bộ đệm đa cấp, một lợi thế quan trọng của ngăn xếp là tính cục bộ của bộ đệm. Phần đầu của ngăn xếp hầu như luôn nằm trong bộ đệm. Bất cứ khi nào bạn có thể làm điều gì đó có tốc độ nhấn bộ nhớ cache lớn, bạn đang đi đúng hướng với bộ xử lý hiện đại. Bộ đệm được áp dụng cho ngăn xếp có nghĩa là các biến cục bộ, tham số, v.v. hầu như luôn nằm trong bộ đệm và tận hưởng mức hiệu suất cao nhất.

Nói tóm lại, việc sử dụng ngăn xếp phát triển cả về phần cứng và phần mềm. Có các mô hình khác (ví dụ tính toán luồng dữ liệu đã được thử trong thời gian dài), tuy nhiên, địa phương của ngăn xếp làm cho nó hoạt động thực sự tốt. Hơn nữa, mã thủ tục chỉ là những gì bộ xử lý muốn, để thực hiện: một lệnh cho nó biết phải làm gì sau một lệnh khác. Khi các hướng dẫn không theo thứ tự tuyến tính, bộ xử lý sẽ chậm đi rất nhiều, ít nhất là vì chúng tôi chưa tìm ra cách thực hiện truy cập ngẫu nhiên nhanh như truy cập tuần tự. (Btw, có các vấn đề tương tự ở mỗi cấp bộ nhớ, từ bộ nhớ cache, bộ nhớ chính, đến đĩa ...)

Giữa hiệu suất đã được chứng minh của các hướng dẫn truy cập tuần tự và hành vi bộ nhớ đệm có lợi của ngăn xếp cuộc gọi, ít nhất hiện tại chúng ta có một mô hình hiệu suất chiến thắng.

(Chúng tôi cũng có thể ném khả năng biến đổi cấu trúc dữ liệu vào công trình ...)

Điều này không có nghĩa là các mô hình lập trình khác không thể hoạt động, đặc biệt là khi chúng có thể được dịch sang các hướng dẫn tuần tự và mô hình ngăn xếp cuộc gọi của phần cứng ngày nay. Nhưng có một lợi thế khác biệt cho các mô hình hỗ trợ phần cứng. Tuy nhiên, mọi thứ không phải lúc nào cũng giữ nguyên, vì vậy chúng ta có thể thấy những thay đổi trong tương lai vì các công nghệ bộ nhớ và bóng bán dẫn khác nhau cho phép song song hơn. Nó luôn là một trò đùa giữa ngôn ngữ lập trình và khả năng phần cứng, vì vậy, chúng ta sẽ thấy!


9
Trên thực tế, GPU vẫn không có ngăn xếp nào cả. Bạn bị cấm không được đệ quy trong GLSL / SPIR-V / OpenCL (không chắc chắn về HLSL, nhưng có lẽ, tôi thấy không có lý do nào khiến nó khác đi). Cách họ thực sự xử lý chức năng gọi "ngăn xếp" là bằng cách sử dụng một số lượng lớn các thanh ghi vô lý.
Tuyến tínhZoetrope

@Jsor: Đó là một chi tiết triển khai ở mức độ lớn, như có thể thấy từ kiến ​​trúc SPARC. Giống như GPU của bạn, SPARC có một bộ thanh ghi khổng lồ, nhưng điều độc đáo ở đây là nó có một cửa sổ trượt, trên đó bao quanh các thanh ghi rất cũ thành một ngăn xếp trong RAM. Vì vậy, nó thực sự là một sự lai tạo giữa hai mô hình. Và SPARC đã không xác định chính xác có bao nhiêu thanh ghi vật lý, chỉ là cửa sổ thanh ghi lớn đến mức nào, do đó việc triển khai khác nhau có thể nằm ở bất kỳ vị trí nào trên quy mô "số lượng thanh ghi khổng lồ" đến "chỉ đủ cho một cửa sổ, trên mỗi lệnh gọi chức năng tràn trực tiếp vào ngăn xếp "
MSalters

Mặt trái của mô hình ngăn xếp cuộc gọi là mảng đó và / hoặc tràn địa chỉ phải được theo dõi rất cẩn thận, vì các chương trình tự sửa đổi như một khai thác là có thể nếu các bit tùy ý của heap có thể thực thi được.
BenPen

14

TL; DR

  • Ngăn xếp cuộc gọi như là một cơ chế gọi chức năng:
    1. Thường được mô phỏng bằng phần cứng nhưng không phải là cơ bản để xây dựng phần cứng
    2. Là nền tảng cho lập trình mệnh lệnh
    3. Không phải là cơ bản để lập trình chức năng
  • Stack như một bản tóm tắt của "từ trước đến trước" (LIFO) là nền tảng cho khoa học máy tính, thuật toán và thậm chí một số lĩnh vực phi kỹ thuật.
  • Một số ví dụ về tổ chức chương trình không sử dụng ngăn xếp cuộc gọi:
    • Phong cách tiếp tục (CPS)
    • Máy trạng thái - một vòng lặp khổng lồ, với mọi thứ được nội tuyến. (Được lấy cảm hứng từ kiến ​​trúc phần mềm Saab Gripen và được gán cho một giao tiếp của Henry Spencer và được sao chép bởi John Carmack.) (Lưu ý số 1)
    • Kiến trúc Dataflow - một mạng lưới các diễn viên được kết nối bởi hàng đợi (FIFO). Các hàng đợi đôi khi được gọi là các kênh.

Phần còn lại của câu trả lời này là một tập hợp ngẫu nhiên các suy nghĩ và giai thoại, và do đó hơi vô tổ chức.


Ngăn xếp mà bạn đã mô tả (như một cơ chế gọi hàm) dành riêng cho lập trình mệnh lệnh.

Dưới đây lập trình bắt buộc, bạn sẽ tìm thấy mã máy. Mã máy có thể mô phỏng ngăn xếp cuộc gọi bằng cách thực hiện một chuỗi hướng dẫn nhỏ.

Bên dưới mã máy, bạn sẽ tìm thấy phần cứng chịu trách nhiệm thực thi phần mềm. Trong khi bộ vi xử lý hiện đại quá phức tạp để được mô tả ở đây, người ta có thể tưởng tượng rằng một thiết kế rất đơn giản tồn tại chậm nhưng vẫn có khả năng thực thi cùng mã máy. Một thiết kế đơn giản như vậy sẽ sử dụng các yếu tố cơ bản của logic kỹ thuật số:

  1. Logic kết hợp, tức là kết nối các cổng logic (và, hoặc, không, ...) Lưu ý rằng "logic tổ hợp" không bao gồm phản hồi.
  2. Bộ nhớ, tức là dép xỏ ngón, chốt, thanh ghi, SRAM, DRAM, v.v.
  3. Một máy trạng thái bao gồm một số logic tổ hợp và một số bộ nhớ, vừa đủ để nó có thể thực hiện một "bộ điều khiển" quản lý phần còn lại của phần cứng.

Các cuộc thảo luận sau đây chứa rất nhiều ví dụ về các cách khác nhau để cấu trúc các chương trình mệnh lệnh.

Cấu trúc của chương trình như thế này sẽ như thế này:

void main(void)
{
    do
    {
        // validate inputs for task 1
        // execute task 1, inlined, 
        // must complete in a deterministically short amount of time
        // and limited to a statically allocated amount of memory
        // ...
        // validate inputs for task 2
        // execute task 2, inlined
        // ...
        // validate inputs for task N
        // execute task N, inlined
    }
    while (true);
    // if this line is reached, tell the programmers to prepare
    // themselves to appear before an accident investigation board.
    return 0; 
}

Phong cách này sẽ phù hợp với các bộ vi điều khiển, tức là cho những người xem phần mềm như một người bạn đồng hành với các chức năng của phần cứng.



@Peteris: Ngăn xếp là cấu trúc dữ liệu LIFO.
Christopher Creutzig

1
Hấp dẫn. Tôi đã có thể nghĩ nó theo cách khác. Ví dụ, FORTRAN là ngôn ngữ lập trình bắt buộc và các phiên bản đầu tiên không sử dụng ngăn xếp cuộc gọi. Tuy nhiên, đệ quy là nền tảng cho lập trình chức năng và tôi không nghĩ có thể thực hiện trong trường hợp chung mà không cần sử dụng ngăn xếp.
TED

@TED ​​- trong triển khai ngôn ngữ chức năng, có cấu trúc dữ liệu ngăn xếp (hoặc thông thường là cây) trong đó biểu thị các phép tính đang chờ xử lý nhưng bạn không nhất thiết phải thực hiện nó bằng các hướng dẫn sử dụng các chế độ địa chỉ hướng ngăn xếp của máy hoặc thậm chí là các lệnh gọi / trả về (theo kiểu lồng nhau / đệ quy - có thể chỉ là một phần của vòng lặp máy trạng thái).
davidbak

@davidbak - IIRC, một thuật toán đệ quy khá nhiều phải được đệ quy theo đuôi để có thể thoát khỏi ngăn xếp. Có thể có một số trường hợp khác mà bạn có thể tối ưu hóa nó, nhưng trong trường hợp chung , bạn phải có một ngăn xếp . Trong thực tế, tôi đã nói rằng có một bằng chứng toán học về sự trôi nổi này ở đâu đó. Câu trả lời này khẳng định đó là định lý Church-Turing (tôi nghĩ dựa trên thực tế là máy Turing sử dụng một ngăn xếp?)
TED

1
@TED ​​- Tôi đồng ý với bạn. Tôi tin rằng thông tin sai lệch ở đây là tôi đã đọc bài viết của OP để nói về kiến trúc hệ thống có ý nghĩa với tôi về kiến trúc máy . Tôi nghĩ rằng những người khác đã trả lời ở đây có cùng sự hiểu biết. Vì vậy, những người trong chúng ta hiểu rằng đó là bối cảnh đã trả lời bằng cách trả lời rằng bạn không cần một ngăn xếp ở cấp độ chế độ hướng dẫn / địa chỉ máy. Nhưng tôi có thể thấy câu hỏi cũng có thể được hiểu là chỉ một hệ thống ngôn ngữ nói chung cần một ngăn xếp cuộc gọi. Câu trả lời đó cũng là không, nhưng vì những lý do khác nhau.
davidbak

11

Không, không nhất thiết.

Đọc Bộ sưu tập Rác giấy cũ của Appel có thể nhanh hơn Phân bổ ngăn xếp . Nó sử dụng kiểu truyền tiếp tục và hiển thị một triển khai stackless.

Cũng lưu ý rằng các kiến ​​trúc máy tính cũ (ví dụ IBM / 360 ) không có bất kỳ thanh ghi ngăn xếp phần cứng nào. Nhưng HĐH và trình biên dịch dành một thanh ghi cho con trỏ ngăn xếp theo quy ước (liên quan đến các quy ước gọi ) để chúng có thể có một ngăn xếp cuộc gọi phần mềm .

Về nguyên tắc, toàn bộ trình biên dịch và trình tối ưu hóa chương trình C có thể phát hiện trường hợp (hơi phổ biến đối với các hệ thống nhúng) trong đó biểu đồ cuộc gọi được biết đến tĩnh và không có bất kỳ đệ quy (hoặc con trỏ hàm) nào. Trong một hệ thống như vậy, mỗi chức năng có thể giữ địa chỉ trả về của nó ở một vị trí tĩnh cố định (và đó là cách Fortran77 hoạt động trong các máy tính thời 1970).

Ngày nay, các bộ xử lý cũng có ngăn xếp cuộc gọi (và hướng dẫn gọi và trả lại máy) nhận biết về bộ đệm CPU .


1
Khá chắc chắn FORTRAN đã ngừng sử dụng các vị trí trả lại tĩnh khi FORTRAN-66 xuất hiện và yêu cầu hỗ trợ cho SUBROUTINEFUNCTION. Mặc dù vậy, bạn đúng với các phiên bản trước (FORTRAN-IV và có thể là WATFIV).
TMN

Và COBOL, tất nhiên. Và điểm tuyệt vời về IBM / 360 - nó đã được sử dụng khá nhiều mặc dù thiếu các chế độ địa chỉ ngăn xếp phần cứng. (R14, tôi tin là vậy?) Và nó có trình biên dịch cho các ngôn ngữ dựa trên ngăn xếp, ví dụ: PL / I, Ada, Algol, C.
davidbak

Thật vậy, tôi đã học 360 ở trường đại học và lúc đầu thấy hoang mang.
JDługosz

1
@ JDługosz Cách tốt nhất để sinh viên hiện đại về kiến ​​trúc máy tính coi 360 là một máy RISC rất đơn giản ... mặc dù có nhiều hơn một định dạng hướng dẫn ... và một vài dị thường như TRTRT.
davidbak

Làm thế nào về "không và thêm đóng gói" để di chuyển một đăng ký? Nhưng "nhánh và liên kết" thay vì ngăn xếp cho địa chỉ trả lại đã trở lại.
JDługosz

10

Bạn đã có một số câu trả lời tốt cho đến nay; hãy để tôi đưa cho bạn một ví dụ không thực tế nhưng mang tính giáo dục cao về cách bạn có thể thiết kế một ngôn ngữ mà không cần khái niệm về ngăn xếp hay "dòng điều khiển". Đây là một chương trình xác định giai thừa:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = f(3)

Chúng tôi đặt chương trình này trong một chuỗi và chúng tôi đánh giá chương trình bằng cách thay thế văn bản. Vì vậy, khi chúng tôi đang đánh giá f(3), chúng tôi thực hiện tìm kiếm và thay thế bằng 3 cho i, như thế này:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = if 3 == 0 then 1 else 3 * f(3 - 1)

Tuyệt quá. Bây giờ chúng tôi thực hiện một thay thế văn bản khác: chúng tôi thấy rằng điều kiện của "nếu" là sai và thực hiện thay thế một chuỗi khác, tạo ra chương trình:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = 3 * f(3 - 1)

Bây giờ chúng tôi thực hiện một chuỗi thay thế khác trên tất cả các biểu thức con liên quan đến các hằng số:

function f(i) => if i == 0 then 1 else i * f(i - 1)
let x = 3 * f(2)

Và bạn thấy điều này diễn ra như thế nào; Tôi sẽ không chuyển dạ. Chúng tôi có thể tiếp tục thực hiện một loạt các thay thế chuỗi cho đến khi chúng tôi xuống let x = 6và chúng tôi đã hoàn thành.

Chúng tôi sử dụng ngăn xếp theo truyền thống cho các biến cục bộ và thông tin tiếp tục; hãy nhớ rằng, một ngăn xếp không cho bạn biết bạn đến từ đâu, nó cho bạn biết bạn sẽ đi đâu tiếp theo với giá trị trả về đó.

Trong mô hình thay thế chuỗi của lập trình, không có "biến cục bộ" trên ngăn xếp; các tham số chính thức được thay thế cho các giá trị của chúng khi hàm được áp dụng cho đối số của nó, thay vì đưa vào bảng tra cứu trên ngăn xếp. Và không có "đi đâu đó tiếp theo" bởi vì đánh giá chương trình chỉ đơn giản là áp dụng các quy tắc đơn giản để thay thế chuỗi để tạo ra một chương trình khác nhưng tương đương.

Bây giờ, tất nhiên, thực sự thay thế chuỗi có lẽ không phải là cách để đi. Nhưng các ngôn ngữ lập trình hỗ trợ "lý luận tương đương" (như Haskell) đang sử dụng hợp lý kỹ thuật này.


3
Retina là một ví dụ về ngôn ngữ lập trình dựa trên Regex sử dụng các phép toán chuỗi để tính toán.
Andrew Piliser

2
@AndrewPiliser Được thiết kế và thực hiện bởi anh chàng tuyệt vời này .
mèo

3

Kể từ khi Parnas xuất bản năm 1972 về các tiêu chí được sử dụng để phân tách các hệ thống thành các mô-đun, người ta đã chấp nhận một cách hợp lý rằng thông tin ẩn trong phần mềm là một điều tốt. Điều này diễn ra sau một cuộc tranh luận dài trong suốt thập niên 60 về phân rã cấu trúc và lập trình mô-đun.

Tính mô đun

Một kết quả cần thiết của mối quan hệ hộp đen giữa các mô-đun được thực hiện bởi các nhóm khác nhau trong bất kỳ hệ thống đa luồng nào đòi hỏi phải có cơ chế cho phép reentrancy và phương tiện để theo dõi biểu đồ cuộc gọi động của hệ thống. Dòng thực thi được kiểm soát phải chuyển cả vào và ra khỏi nhiều mô-đun.

Phạm vi năng động

Ngay khi phạm vi từ vựng không đủ để theo dõi hành vi động, thì cần có một số sổ sách kế toán thời gian chạy để theo dõi sự khác biệt.

Với bất kỳ luồng nào (theo định nghĩa) chỉ có một con trỏ lệnh hiện tại, ngăn xếp LIFO thích hợp để theo dõi từng lệnh gọi.

Ngoại lệ

Vì vậy, trong khi mô hình tiếp tục không duy trì cấu trúc dữ liệu rõ ràng cho ngăn xếp, vẫn có sự gọi các mô-đun lồng nhau phải được duy trì ở đâu đó!

Ngay cả các ngôn ngữ khai báo hoặc duy trì lịch sử đánh giá, hoặc ngược lại làm phẳng kế hoạch thực hiện vì lý do hiệu suất và duy trì tiến trình theo một cách khác.

Cấu trúc vòng lặp vô tận được xác định bởi rwong là phổ biến trong các ứng dụng có độ tin cậy cao với lập lịch tĩnh không tuân theo nhiều cấu trúc lập trình phổ biến nhưng yêu cầu toàn bộ ứng dụng phải được coi là một hộp trắng không có ẩn thông tin quan trọng.

Nhiều vòng lặp vô tận đồng thời không yêu cầu bất kỳ cấu trúc nào để giữ địa chỉ trả về vì chúng không gọi các hàm, làm cho câu hỏi được đưa ra. Nếu chúng giao tiếp bằng cách sử dụng các biến được chia sẻ, thì chúng có thể dễ dàng suy biến thành các tương tự địa chỉ trả về kiểu Fortran cũ.


1
Bạn vẽ mình trong một góc bằng cách giả sử " bất kỳ hệ thống đa luồng" nào. Các máy trạng thái hữu hạn được ghép nối có thể có nhiều luồng khi triển khai, nhưng không yêu cầu ngăn xếp LIFO. Không có giới hạn nào trong FSM là bạn quay trở lại bất kỳ trạng thái nào trước đó, hãy để một mình theo thứ tự LIFO. Vì vậy, đó là một hệ thống đa luồng thực sự mà nó không giữ được. Và nếu bạn giới hạn bản thân trong một định nghĩa đa luồng là "ngăn xếp cuộc gọi chức năng độc lập song song" thì bạn sẽ có một định nghĩa vòng tròn.
MSalters

Tôi không đọc câu hỏi theo cách đó. OP quen thuộc với các cuộc gọi chức năng, nhưng hỏi về các hệ thống khác .
MSalters

@MSalters Cập nhật để kết hợp các vòng lặp vô tận đồng thời. Mô hình là hợp lệ, nhưng giới hạn khả năng mở rộng. Tôi đề nghị rằng ngay cả các máy trạng thái vừa phải kết hợp các lệnh gọi chức năng để cho phép tái sử dụng mã.
Pekka

2

Tất cả các máy tính lớn cũ (Hệ thống IBM / 360) hoàn toàn không có khái niệm về một ngăn xếp. Ví dụ, trên 260, các tham số được xây dựng ở một vị trí cố định trong bộ nhớ và khi chương trình con được gọi, nó được gọi bằng R1cách chỉ vào khối tham số và R14chứa địa chỉ trả về. Thói quen được gọi, nếu nó muốn gọi một chương trình con khác, sẽ phải lưu trữ R14ở một vị trí đã biết trước khi thực hiện cuộc gọi đó.

Điều này đáng tin cậy hơn nhiều so với ngăn xếp vì mọi thứ có thể được lưu trữ trong các vị trí bộ nhớ cố định được thiết lập tại thời điểm biên dịch và có thể được đảm bảo 100% rằng các quy trình sẽ không bao giờ hết ngăn xếp. Không có "Phân bổ 1MB và vượt qua ngón tay của bạn" mà chúng ta phải làm ngày nay.

Các cuộc gọi chương trình con đệ quy được cho phép trong PL / I bằng cách chỉ định từ khóa RECURSIVE. Chúng có nghĩa là bộ nhớ được sử dụng bởi chương trình con là động chứ không phải phân bổ tĩnh. Nhưng các cuộc gọi đệ quy cũng hiếm như bây giờ.

Hoạt động không chồng cũng làm cho đa luồng lớn dễ dàng hơn nhiều, đó là lý do tại sao các nỗ lực thường được thực hiện để làm cho các ngôn ngữ hiện đại trở nên vô dụng. Chẳng có lý do nào cả, ví dụ, tại sao trình biên dịch C ++ không thể được sửa đổi back-end để sử dụng bộ nhớ được cấp phát động thay vì ngăn xếp.

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.