Mục đích của một ngăn xếp là gì? Tại sao chúng ta cần nó?


320

Vì vậy, tôi đang học MSIL ngay bây giờ để học cách gỡ lỗi các ứng dụng C # .NET của mình.

Tôi đã luôn tự hỏi: mục đích của ngăn xếp là gì?

Chỉ cần đặt câu hỏi của tôi trong ngữ cảnh:
Tại sao có sự chuyển từ bộ nhớ sang ngăn xếp hoặc "tải?" Mặt khác, tại sao có sự chuyển từ ngăn xếp sang bộ nhớ hoặc "lưu trữ"? Tại sao không chỉ có tất cả chúng được đặt trong bộ nhớ?

  • Có phải vì nó nhanh hơn?
  • Có phải vì nó dựa trên RAM?
  • Cho hiệu quả?

Tôi đang cố gắng nắm bắt điều này để giúp tôi hiểu sâu hơn về mã CIL .


28
Ngăn xếp là một phần của bộ nhớ, giống như heap là một phần khác của bộ nhớ.
CodeInChaos

@CodeInChaos bạn đang nói về loại giá trị so với loại tham chiếu? hoặc là giống nhau về các mã IL? ... Tôi biết rằng stack chỉ nhanh hơn và hiệu quả hơn heap (nhưng đó là trong thế giới loại giá trị / ref .. mà tôi không biết có giống như ở đây không?)
Jan Carlo Viray

15
@CodeInChaos - Tôi nghĩ rằng ngăn xếp mà Jan tham chiếu là máy ngăn xếp mà IL được viết ngược lại, trái ngược với vùng bộ nhớ chấp nhận các khung ngăn xếp trong các lệnh gọi hàm. Chúng là hai ngăn xếp khác nhau và sau JIT, ngăn xếp IL không tồn tại (trên x86, dù sao)
Damien_The_Unbeliever

4
Kiến thức MSIL sẽ giúp bạn gỡ lỗi các ứng dụng .NET như thế nào?
Piotr Perak

1
Trên các máy hiện đại, hành vi lưu trữ mã của bộ đệm là một trình tạo và phá vỡ hiệu năng. Ký ức ở khắp mọi nơi. Stack là, thường, chỉ ở đây. Giả sử rằng ngăn xếp là một thứ có thật, và không chỉ là một khái niệm được sử dụng để thể hiện hoạt động của một số mã. Khi triển khai nền tảng chạy MSIL, không có yêu cầu nào về khái niệm ngăn xếp làm cho phần cứng thực sự đẩy các bit xung quanh.
Tái lập lại

Câu trả lời:


441

CẬP NHẬT: Tôi thích câu hỏi này rất nhiều, tôi đã biến nó thành chủ đề của blog của mình vào ngày 18 tháng 11 năm 2011 . Cảm ơn vì câu hỏi tuyệt vời của bạn!

Tôi đã luôn tự hỏi: mục đích của ngăn xếp là gì?

Tôi giả sử bạn có nghĩa là ngăn xếp đánh giá của ngôn ngữ MSIL chứ không phải ngăn xếp trên mỗi luồng thực tế khi chạy.

Tại sao có sự chuyển từ bộ nhớ sang ngăn xếp hoặc "tải?" Mặt khác, tại sao có sự chuyển từ ngăn xếp sang bộ nhớ hoặc "lưu trữ"? Tại sao không chỉ có tất cả chúng được đặt trong bộ nhớ?

MSIL là ngôn ngữ "máy ảo". Các trình biên dịch như trình biên dịch C # tạo CIL , và sau đó trong thời gian chạy, trình biên dịch khác gọi là trình biên dịch JIT (Just In Time) biến IL thành mã máy thực tế có thể thực thi.

Vì vậy, trước tiên hãy trả lời câu hỏi "tại sao lại có MSIL?" Tại sao không có trình biên dịch C # viết mã máy?

Bởi vì nó rẻ hơn để làm theo cách này. Giả sử chúng ta đã không làm theo cách đó; giả sử mỗi ngôn ngữ phải có trình tạo mã máy riêng. Bạn có hai mươi ngôn ngữ khác nhau: C #, JScript .NET , Visual Basic, IronPython , F # ... Và giả sử bạn có mười bộ xử lý khác nhau. Có bao nhiêu trình tạo mã bạn phải viết? 20 x 10 = 200 máy tạo mã. Đó là rất nhiều công việc. Bây giờ giả sử bạn muốn thêm một bộ xử lý mới. Bạn phải viết trình tạo mã cho nó hai mươi lần, một lần cho mỗi ngôn ngữ.

Hơn nữa, đó là công việc khó khăn và nguy hiểm. Viết các trình tạo mã hiệu quả cho các chip mà bạn không phải là chuyên gia là một công việc khó khăn! Các nhà thiết kế trình biên dịch là các chuyên gia về phân tích ngữ nghĩa của ngôn ngữ của họ, chứ không phải phân bổ đăng ký hiệu quả các bộ chip mới.

Bây giờ giả sử chúng ta làm theo cách CIL. Bạn phải viết bao nhiêu máy phát điện CIL? Một cho mỗi ngôn ngữ. Bạn phải viết bao nhiêu trình biên dịch JIT? Một cho mỗi bộ xử lý. Tổng cộng: 20 + 10 = 30 trình tạo mã. Hơn nữa, trình tạo ngôn ngữ-CIL dễ viết vì CIL là ngôn ngữ đơn giản và trình tạo mã CIL-to-machine-code cũng dễ viết vì CIL là ngôn ngữ đơn giản. Chúng tôi loại bỏ tất cả những điều phức tạp của C # và VB và không chú ý và "hạ thấp" mọi thứ thành một ngôn ngữ đơn giản, dễ viết jitter cho.

Có một ngôn ngữ trung gian làm giảm đáng kể chi phí sản xuất một trình biên dịch ngôn ngữ mới . Nó cũng làm giảm đáng kể chi phí hỗ trợ một con chip mới. Bạn muốn hỗ trợ một con chip mới, bạn tìm một số chuyên gia về con chip đó và nhờ họ viết một jitter CIL và bạn đã hoàn thành; sau đó bạn hỗ trợ tất cả các ngôn ngữ trên chip của bạn.

OK, vì vậy chúng tôi đã thiết lập lý do tại sao chúng tôi có MSIL; bởi vì có một ngôn ngữ trung gian làm giảm chi phí. Tại sao ngôn ngữ là "máy xếp"?

Bởi vì các máy stack là khái niệm rất đơn giản cho các nhà văn biên dịch ngôn ngữ để đối phó. Ngăn xếp là một cơ chế đơn giản, dễ hiểu để mô tả các tính toán. Các máy stack cũng về mặt khái niệm rất dễ dàng cho các nhà văn trình biên dịch JIT đối phó. Sử dụng một ngăn xếp là một sự trừu tượng đơn giản hóa, và do đó, một lần nữa, nó làm giảm chi phí của chúng tôi .

Bạn hỏi "tại sao lại có một chồng?" Tại sao không làm mọi thứ trực tiếp ra khỏi bộ nhớ? Chà, hãy nghĩ về điều đó. Giả sử bạn muốn tạo mã CIL cho:

int x = A() + B() + C() + 10;

Giả sử chúng ta có quy ước "thêm", "gọi", "lưu trữ" và cứ thế, luôn lấy các đối số của chúng ra khỏi ngăn xếp và đặt kết quả của chúng (nếu có) vào ngăn xếp. Để tạo mã CIL cho C # này, chúng ta chỉ cần nói một số thứ như:

load the address of x // The stack now contains address of x
call A()              // The stack contains address of x and result of A()
call B()              // Address of x, result of A(), result of B()
add                   // Address of x, result of A() + B()
call C()              // Address of x, result of A() + B(), result of C()
add                   // Address of x, result of A() + B() + C()
load 10               // Address of x, result of A() + B() + C(), 10
add                   // Address of x, result of A() + B() + C() + 10
store in address      // The result is now stored in x, and the stack is empty.

Bây giờ giả sử chúng tôi đã làm nó mà không có một ngăn xếp. Chúng tôi sẽ thực hiện theo cách của bạn, trong đó mọi opcode sẽ lấy địa chỉ của toán hạng của nó và địa chỉ mà nó lưu kết quả của nó :

Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...

Bạn thấy điều này diễn ra như thế nào? Mã của chúng tôi đang trở nên rất lớn bởi vì chúng tôi phải phân bổ rõ ràng tất cả lưu trữ tạm thời mà thông thường theo quy ước chỉ cần đi vào ngăn xếp . Tồi tệ hơn, bản thân các mã của chúng ta đang trở nên to lớn bởi vì tất cả chúng bây giờ phải lấy làm đối số cho địa chỉ mà chúng sẽ ghi kết quả của chúng vào và địa chỉ của mỗi toán hạng. Một lệnh "thêm" biết rằng nó sẽ lấy hai thứ ra khỏi ngăn xếp và đặt một thứ lên có thể là một byte đơn. Một hướng dẫn thêm có hai địa chỉ toán hạng và địa chỉ kết quả sẽ rất lớn.

Chúng tôi sử dụng opcodes dựa trên ngăn xếpngăn xếp giải quyết vấn đề phổ biến . Cụ thể: Tôi muốn phân bổ một số lưu trữ tạm thời, sử dụng nó rất sớm và sau đó loại bỏ nó một cách nhanh chóng khi tôi hoàn thành . Bằng cách đưa ra giả định rằng chúng ta có một ngăn xếp theo ý của chúng ta, chúng ta có thể làm cho các opcode rất nhỏ và mã rất ngắn gọn.

CẬP NHẬT: Một số suy nghĩ bổ sung

Ngẫu nhiên, ý tưởng giảm chi phí mạnh mẽ bằng cách (1) chỉ định một máy ảo, (2) trình biên dịch viết nhắm vào ngôn ngữ VM và (3) viết các triển khai VM trên nhiều loại phần cứng, hoàn toàn không phải là một ý tưởng mới . Nó không bắt nguồn từ MSIL, LLVM, Java bytecode hoặc bất kỳ cơ sở hạ tầng hiện đại nào khác. Việc thực hiện sớm nhất của chiến lược này mà tôi biết là máy pcode từ năm 1966.

Cá nhân tôi lần đầu tiên nghe về khái niệm này là khi tôi tìm hiểu cách những người triển khai Infocom quản lý để Zork chạy trên rất nhiều máy khác nhau rất tốt. Họ đã chỉ định một máy ảo được gọi là máy Z và sau đó tạo trình giả lập máy Z cho tất cả phần cứng mà họ muốn chạy trò chơi của họ. Điều này có thêm lợi ích to lớn mà họ có thể thực hiện quản lý bộ nhớ ảo trên các hệ thống 8 bit nguyên thủy; một trò chơi có thể lớn hơn phù hợp với bộ nhớ vì họ chỉ có thể trang mã từ đĩa khi họ cần và loại bỏ nó khi họ cần tải mã mới.


63
Ôi. Đó chỉ là chính xác những gì tôi đang tìm kiếm. Cách tốt nhất để có câu trả lời là lấy một từ chính nhà phát triển chính. Cảm ơn thời gian và tôi chắc chắn rằng điều này sẽ giúp tất cả những ai thắc mắc về sự phức tạp của trình biên dịch và MSIL. Cảm ơn Eric.
Jan Carlo Viray

18
Đó là một câu trả lời tuyệt vời. Nhắc tôi tại sao tôi đọc blog của bạn mặc dù tôi là một người Java. ;-)
jprete

34
@JanCarloViray: Bạn rất hoan nghênh! Tôi lưu ý rằng tôi là một nhà phát triển chủ yếu, chứ không phải các nhà phát triển chính. Có một vài người trong nhóm này với chức danh công việc đó và tôi thậm chí không phải là người cao cấp nhất trong số họ.
Eric Lippert

17
@Eric: Nếu / khi bạn ngừng yêu thích viết mã, bạn nên xem xét việc dạy lập trình viên. Bên cạnh niềm vui, bạn có thể giết chết mà không chịu áp lực kinh doanh. Sự tinh tế tuyệt vời là những gì bạn có trong khu vực đó (và sự kiên nhẫn tuyệt vời, tôi có thể thêm vào). Tôi nói rằng như một giảng viên đại học cũ.
Alan

19
Khoảng 4 đoạn trong tôi đã tự nói với bản thân mình "Điều này nghe giống Eric", đến ngày 5 hoặc 6 tôi đã tốt nghiệp "Yep, chắc chắn là Eric" :) Một câu trả lời thực sự & toàn diện về mặt sử thi.
Nhị phân nhị phân

86

Hãy nhớ rằng khi bạn nói về MSIL thì bạn đang nói về các hướng dẫn cho một máy ảo . VM được sử dụng trong .NET là một máy ảo dựa trên ngăn xếp. Trái ngược với VM dựa trên đăng ký, Dalvik VM được sử dụng trong các hệ điều hành Android là một ví dụ về điều đó.

Ngăn xếp trong VM là ảo, tùy thuộc vào trình thông dịch hoặc trình biên dịch đúng lúc để dịch các lệnh VM thành mã thực tế chạy trên bộ xử lý. Trong trường hợp .NET hầu như luôn luôn là một jitter, tập lệnh MSIL được thiết kế để được jited ngay từ đầu. Trái ngược với mã byte Java chẳng hạn, nó có các hướng dẫn riêng biệt cho các hoạt động trên các loại dữ liệu cụ thể. Mà làm cho nó được tối ưu hóa để được giải thích. Một trình thông dịch MSIL thực sự tồn tại mặc dù, nó được sử dụng trong .NET Micro Framework. Chạy trên các bộ xử lý có tài nguyên rất hạn chế, không thể đủ RAM để lưu mã máy.

Mô hình mã máy thực tế là hỗn hợp, có cả ngăn xếp và thanh ghi. Một trong những công việc lớn của trình tối ưu hóa mã JIT là đưa ra các cách để lưu trữ các biến được lưu trên ngăn xếp trong các thanh ghi, do đó cải thiện đáng kể tốc độ thực thi. Một jitter Dalvik có vấn đề ngược lại.

Ngăn xếp máy là một thiết bị lưu trữ rất cơ bản đã có trong các thiết kế bộ xử lý trong một thời gian rất dài. Nó có tính tham chiếu địa phương rất tốt, một tính năng rất quan trọng trên các CPU hiện đại, nhai dữ liệu nhanh hơn rất nhiều so với RAM có thể cung cấp cho nó và hỗ trợ đệ quy. Thiết kế ngôn ngữ bị ảnh hưởng nặng nề bởi có một ngăn xếp, có thể nhìn thấy để hỗ trợ cho các biến cục bộ và phạm vi giới hạn trong thân phương thức. Một vấn đề quan trọng với ngăn xếp là vấn đề mà trang web này được đặt tên.


2
+1 cho một lời giải thích rất chi tiết và +100 (nếu tôi có thể) để biết thêm CHI TIẾT so với các hệ thống và ngôn ngữ khác :)
Jan Carlo Viray

4
Tại sao Dalvik là máy Đăng ký? Sicne chủ yếu nhắm vào Bộ xử lý ARM. Bây giờ, x86 có cùng số lượng thanh ghi nhưng là một CISC, chỉ có 4 trong số chúng thực sự có thể sử dụng để lưu trữ cục bộ vì phần còn lại được sử dụng ngầm trong các hướng dẫn chung. Mặt khác, các kiến ​​trúc ARM có nhiều thanh ghi hơn có thể được sử dụng để lưu trữ cục bộ, do đó chúng tạo điều kiện cho một mô hình thực thi dựa trên thanh ghi.
Julian Rudolph

1
@JohannesRudolph Điều đó đã không đúng trong gần hai thập kỷ nay. Chỉ vì hầu hết các trình biên dịch C ++ vẫn nhắm mục tiêu tập lệnh x86 của thập niên 90 không có nghĩa là bản thân x86 là không đủ. Haswell có 168 thanh ghi số nguyên mục đích chung và 168 thanh ghi AVX GP, chẳng hạn - nhiều hơn bất kỳ CPU ARM nào tôi biết. Bạn có thể sử dụng tất cả những thứ từ lắp ráp x86 (hiện đại) theo bất kỳ cách nào bạn muốn. Đổ lỗi cho trình biên dịch, không phải kiến ​​trúc / CPU. Trên thực tế, đó là một trong những lý do tại sao quá trình biên dịch trung gian rất hấp dẫn - một mã nhị phân, mã tốt nhất cho một CPU nhất định; không có mucking xung quanh với kiến ​​trúc tuổi 90.
Luaan

2
@JohannesRudolph Trình biên dịch .NET JIT thực sự sử dụng các thanh ghi khá nặng; ngăn xếp chủ yếu là sự trừu tượng của máy ảo IL, mã thực sự chạy trên CPU của bạn rất khác nhau. Các cuộc gọi phương thức có thể là thanh ghi chuyển qua, người địa phương có thể được nâng lên thanh ghi ... Lợi ích chính của ngăn xếp trong mã máy là sự cô lập mà nó mang lại cho các cuộc gọi chương trình con - nếu bạn đặt cục bộ vào một thanh ghi, một lệnh gọi hàm có thể thực hiện bạn mất giá trị đó và bạn không thể nói được.
Luaan

1
@RahulAgarwal Mã máy được tạo có thể hoặc không thể sử dụng ngăn xếp cho bất kỳ giá trị địa phương hoặc trung gian cụ thể nào. Trong IL, mọi đối số và cục bộ đều nằm trên ngăn xếp - nhưng trong mã máy, điều này không đúng (được phép, nhưng không bắt buộc). Một số thứ hữu ích trên stack và chúng được đặt trên stack. Một số thứ hữu ích trên heap, và chúng được đưa vào heap. Một số thứ không cần thiết chút nào, hoặc chỉ cần một vài phút trong đăng ký. Các cuộc gọi có thể được loại bỏ hoàn toàn (nội tuyến) hoặc các đối số của chúng có thể được chuyển qua trong các thanh ghi. JIT có rất nhiều tự do.
Luaan

20

Có một bài viết Wikipedia rất thú vị / chi tiết về điều này, Ưu điểm của bộ hướng dẫn máy stack . Tôi sẽ cần trích dẫn nó hoàn toàn, vì vậy việc đặt một liên kết sẽ dễ dàng hơn. Tôi chỉ đơn giản là trích dẫn các tiêu đề phụ

  • Mã đối tượng rất nhỏ gọn
  • Trình biên dịch đơn giản / thông dịch viên đơn giản
  • Trạng thái bộ xử lý tối thiểu

-1 @xanatos Bạn có thể thử và tóm tắt các tiêu đề bạn đã thực hiện không?
Tim Lloyd

@chibacity Nếu tôi muốn tóm tắt chúng, tôi đã có câu trả lời. Tôi đã cố gắng cứu vãn một liên kết rất tốt.
xanatos

@xanatos Tôi hiểu mục tiêu của bạn, nhưng chia sẻ liên kết đến một bài viết lớn trên wikipedia như vậy không phải là một câu trả lời tuyệt vời. Không khó để tìm thấy chỉ bằng cách googling. Mặt khác, Hans có một câu trả lời hay.
Tim Lloyd

@chibacity OP có lẽ lười biếng khi không tìm kiếm trước. Người trả lời ở đây đã đưa ra một liên kết tốt (mà không mô tả nó). Hai tệ nạn làm một điều tốt :-) Và tôi sẽ nâng cao Hans.
xanatos

để trả lời và @xanatos +1 cho liên kết TUYỆT VỜI. Tôi đang đợi ai đó tóm tắt đầy đủ và có câu trả lời gói kiến ​​thức .. nếu Hans không đưa ra câu trả lời, tôi sẽ đưa ra câu trả lời được chấp nhận của bạn .. đó chỉ là một liên kết, vì vậy nó không phải là một liên kết công bằng cho Hans, người đã nỗ lực rất nhiều cho câu trả lời của mình .. :)
Jan Carlo Viray

8

Để thêm một chút nữa cho câu hỏi ngăn xếp. Khái niệm ngăn xếp xuất phát từ thiết kế CPU trong đó mã máy trong đơn vị logic số học (ALU) hoạt động trên các toán hạng được đặt trên ngăn xếp. Ví dụ, một phép toán nhân có thể lấy hai toán hạng trên cùng từ ngăn xếp, nhân chúng và đặt kết quả trở lại vào ngăn xếp. Ngôn ngữ máy thường có hai chức năng cơ bản để thêm và xóa toán hạng khỏi ngăn xếp; Push và POP. Trong nhiều dsp của cpu (bộ xử lý tín hiệu số) và bộ điều khiển máy (chẳng hạn như điều khiển máy giặt), ngăn xếp được đặt trên chính con chip. Điều này cho phép truy cập nhanh hơn vào ALU và hợp nhất các chức năng cần thiết vào một chip đơn.


5

Nếu khái niệm stack / heap không được tuân theo và dữ liệu được tải vào vị trí bộ nhớ ngẫu nhiên HOẶC dữ liệu được lưu trữ từ các vị trí bộ nhớ ngẫu nhiên ... nó sẽ rất không có cấu trúc và không được quản lý.

Các khái niệm này được sử dụng để lưu trữ dữ liệu trong một cấu trúc được xác định trước để cải thiện hiệu suất, sử dụng bộ nhớ ... và do đó được gọi là cấu trúc dữ liệu.


4

Người ta có thể có một hệ thống làm việc mà không có ngăn xếp, bằng cách sử dụng kiểu mã hóa tiếp tục . Sau đó, các khung gọi trở thành các phần tiếp theo được phân bổ trong đống rác được thu gom (bộ thu gom rác sẽ cần một số ngăn xếp).

Xem các tác phẩm cũ của Andrew Appel: Biên dịch với ContinuationsBộ sưu tập rác có thể nhanh hơn Phân bổ ngăn xếp

(Hôm nay anh ấy có thể hơi sai một chút vì vấn đề bộ đệm)


0

Tôi đã tìm kiếm "ngắt" và không ai coi đó là một lợi thế. Đối với mỗi thiết bị làm gián đoạn vi điều khiển hoặc bộ xử lý khác, thường có các thanh ghi được đẩy lên ngăn xếp, một thói quen dịch vụ ngắt được gọi và khi hoàn thành, các thanh ghi được bật ra khỏi ngăn xếp và đặt trở lại nơi chúng là. Sau đó, con trỏ lệnh được khôi phục và hoạt động bình thường xuất hiện ở nơi nó dừng lại, gần như là sự gián đoạn không bao giờ xảy ra. Với ngăn xếp, bạn thực sự có thể có một vài thiết bị (về mặt lý thuyết) làm gián đoạn lẫn nhau và tất cả chỉ hoạt động - vì ngăn xếp.

Ngoài ra còn có một họ các ngôn ngữ dựa trên ngăn xếp được gọi là ngôn ngữ nối . Chúng là tất cả (tôi tin) các ngôn ngữ chức năng, bởi vì ngăn xếp là một tham số ngầm được truyền vào, và ngăn xếp thay đổi là một trả về ngầm từ mỗi hàm. Cả ForthFactor (rất xuất sắc) là những ví dụ, cùng với những người khác. Yếu tố đã được sử dụng tương tự như Lua, cho các trò chơi kịch bản, và được viết bởi Slava Pestov, một thiên tài hiện đang làm việc tại Apple. Google TechTalk của anh ấy trên youtube tôi đã xem một vài lần. Anh ấy nói về những người xây dựng Boa, nhưng tôi không chắc anh ấy có ý gì ;-).

Tôi thực sự nghĩ rằng một số máy ảo hiện tại, như JVM, CIL của Microsoft và thậm chí cả máy ảo mà tôi thấy được viết cho Lua, nên được viết bằng một số ngôn ngữ dựa trên ngăn xếp này, để chúng có thể di chuyển đến nhiều nền tảng hơn. Tôi nghĩ rằng các ngôn ngữ ghép nối này bằng cách nào đó đang thiếu các cuộc gọi của họ như các bộ công cụ tạo VM và các nền tảng di động. Thậm chí còn có pForth, một Forth "di động" được viết bằng ANSI C, có thể được sử dụng cho tính di động phổ quát hơn nữa. Bất cứ ai cũng cố gắng biên dịch nó bằng Emscripten hoặc WebAssugging?

Với các ngôn ngữ dựa trên ngăn xếp, có một kiểu mã được gọi là zero-point, bởi vì bạn chỉ có thể liệt kê các hàm được gọi mà không chuyển bất kỳ tham số nào (đôi khi). Nếu các hàm khớp với nhau một cách hoàn hảo, bạn sẽ không có gì ngoài danh sách tất cả các hàm 0 điểm và đó sẽ là ứng dụng của bạn (về mặt lý thuyết). Nếu bạn đi sâu vào Forth hoặc Factor, bạn sẽ thấy những gì tôi đang nói.

Tại Easy Forth , một hướng dẫn trực tuyến hay được viết bằng JavaScript, đây là một mẫu nhỏ (lưu ý "sq sq sq sq" như một ví dụ về kiểu gọi không điểm):

: sq dup * ;  ok
2 sq . 4  ok
: ^4 sq sq ;  ok
2 ^4 . 16  ok
: ^8 sq sq sq sq ;  ok
2 ^8 . 65536  ok

Ngoài ra, nếu bạn xem nguồn trang web Easy Forth, bạn sẽ thấy ở phía dưới rằng nó rất mô-đun, được viết bằng khoảng 8 tệp JavaScript.

Tôi đã chi rất nhiều tiền cho mỗi cuốn sách Forth mà tôi có thể có được trong nỗ lực đồng hóa Forth, nhưng giờ tôi mới bắt đầu tìm hiểu kỹ hơn. Tôi muốn dành một cái đầu cho những người đến sau, nếu bạn thực sự muốn lấy nó (tôi phát hiện ra điều này quá muộn), hãy lấy cuốn sách về FigForth và thực hiện điều đó. Forths thương mại đều quá phức tạp, và điều tuyệt vời nhất về Forth là có thể hiểu toàn bộ hệ thống, từ trên xuống dưới. Bằng cách nào đó, Forth thực hiện toàn bộ môi trường phát triển trên bộ xử lý mới và mặc dù cầnvì điều đó dường như đã vượt qua với C trên mọi thứ, nó vẫn hữu ích như một nghi thức thông qua để viết một Forth từ đầu. Vì vậy, nếu bạn chọn thực hiện việc này, hãy thử cuốn sách FigForth - đó là một số Forth được triển khai đồng thời trên nhiều bộ xử lý. Một loại Rosetta Stone of Forths.

Tại sao chúng ta cần một ngăn xếp - hiệu quả, tối ưu hóa, điểm không, lưu các thanh ghi khi bị gián đoạn và đối với các thuật toán đệ quy, đó là "hình dạng phù hợ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.