5. Nguyên tắc lập trình hội
Các chương trước đã bao phủ tập lệnh ARM và sử dụng lắp ráp ARM. Bây giờ chúng ta đang ở trong một vị trí để bắt đầu lập trình đúng cách. Vì chúng ta đang giả sử bạn có thể lập trình trong BASIC, hầu hết các chương này có thể được xem như là một khóa học chuyển đổi. Nó minh họa bằng những ví dụ như thế nào các kỹ thuật lập trình mà bạn sử dụng khi viết trong một ngôn ngữ cấp cao dịch vào lắp ráp.
5.1 cấu trúc điều khiển
một số lý thuyết
Một chương trình được tạo thành hướng dẫn mà thực hiện các giải pháp cho một vấn đề. Bất kỳ giải pháp như vậy, hoặc thuật toán, có thể được thể hiện trong điều khoản của một số khái niệm cơ bản. Hai trong số các quan trọng nhất là chương trình phân hủy và dòng điều khiển.
Các thành phần của một chương trình liên quan đến cách nó được chia thành các đơn vị nhỏ hơn mà giải quyết một phần cụ thể của vấn đề. Khi kết hợp, các đơn vị này, hoặc tiểu chương trình, hình thành một giải pháp cho các vấn đề như một toàn thể. Trong ngôn ngữ cấp cao như BASIC và Pascal, cơ chế thủ tục cho phép phân hủy thực tế của chương trình thành các đơn vị nhỏ hơn, dễ quản lý hơn. Ở góc độ ngôn ngữ lắp ráp, các chương trình con thực hiện các chức năng tương tự.
Dòng điều khiển trong một chương trình là thứ tự mà các hướng dẫn thi hành. Ba loại quan trọng của cấu trúc điều khiển đã được xác định là: trình tự, lặp đi lặp lại, và quyết định.
Một trình tự hướng dẫn đơn giản chỉ là hành vi thực hiện hướng dẫn cái khác trong thứ tự mà chúng xuất hiện trong chương trình. Trên ARM, hành động này là một hệ quả của PC được tăng lên sau mỗi lệnh, trừ khi nó được thay đổi một cách rõ ràng.
Loại thứ hai của dòng điều khiển là quyết định: khả năng thực hiện một chuỗi các hướng dẫn chỉ nếu một điều kiện nhất định giữ (ví dụ NẾU ... THÌ ... ). Phần mở rộng này là khả năng để mất hai, đường loại trừ lẫn nhau riêng biệt ( IF ... THEN ... ELSE ...), và một quyết định đa chiều dựa trên một số giá trị ( ON ... PROC ... ). Tất cả những cấu trúc có sẵn cho các lập trình hợp ngữ, nhưng anh ấy phải được rõ ràng hơn về ý định của mình.
Lặp đi lặp lại có nghĩa là vòng lặp. Thực hiện cùng một bộ các hướng dẫn hơn và hơn nữa là một trong những sở trường của máy tính. Ngôn ngữ cấp cao cung cấp các cấu trúc như REPEAT .. UNTIL và CHO ...Tiếp theo để thực hiện lặp đi lặp lại. Một lần nữa, trong assembler bạn phải đánh vần ra các hành động mong muốn một ít rõ ràng hơn, sử dụng lạc hậu chi nhánh (có lẽ có điều kiện).
một số thực hành
Sau khi nói về cấu trúc chương trình một cách khá trừu tượng, bây giờ chúng ta xem xét một số ví dụ cụ thể. Bởi vì chúng ta đang giả sử bạn có một số kiến thức của BASIC, hoặc ngôn ngữ cấp cao tương tự, các cấu trúc tìm thấy trong đó sẽ được sử dụng như là một điểm khởi đầu. Chúng tôi sẽ trình bày các bản sao trung thành của IF ... THEN ... ELSE , CHO ... TIẾP , vv sử dụng ARM lắp ráp. Tuy nhiên, một trong những lợi thế của việc sử dụng ngôn ngữ lắp ráp là tính linh hoạt của nó; bạn không nên giới hạn mình vào một cách mù quáng bắt chước các kỹ thuật bạn sử dụng trong BASIC, nếu một số phương pháp thích hợp hơn khác cho thấy chính nó.
Chức vụ độc lập
Một số ví dụ dưới (ví dụ các ON ... PROC thực hiện bằng cách sử dụng bảng phân nhánh) có vẻ hơi phức tạp hơn cần thiết. Đặc biệt, việc giải quyết các dữ liệu và các thủ tục được thực hiện không phải bằng cách tải các địa chỉ vào sổ đăng ký, nhưng bằng cách thực hiện một phép tính (thường là 'ẩn' trong một ADR chỉ thị) để có được một địa chỉ. Phức tạp dường như không cần thiết này là do mong muốn làm cho các chương trình độc lập vị.
mã vị trí độc lập có tài sản mà nó sẽ thực hiện một cách chính xác không có vấn đề ở đâu trong bộ nhớ nó được nạp. Để sở hữu tài sản này, các mã phải không chứa tham chiếu đến đối tượng tuyệt đối. Đó là, bất kỳ dữ liệu nội bộ hoặc thói quen truy cập phải được tham chiếu đối với một số điểm cố định trong chương trình với. Khi bù đắp từ các vị trí cần thiết để các điểm cố định vẫn không đổi, địa chỉ của đối tượng có thể được tính toán không phân biệt nơi chương trình đã được nạp. Thông thường, các địa chỉ được tính toán đối với các hướng dẫn hiện hành. Bạn thường sẽ thấy hướng dẫn của các hình thức:
.here ADD ptr, pc, # object- (ở đây + 8)
để có được địa chỉ của đối tượng trong sổ đăng ký ptr . Phần 8 xảy ra bởi vì các máy tính luôn luôn là hai hướng dẫn (8 byte) thêm vào so với hướng dẫn được thực hiện, do pipelining.
Đó là vì các tần số mà tính toán này cây trồng lên rằng ADR chỉ được cung cấp. Như chúng tôi đã giải thích trong Chương Bốn, dòng trên có thể được viết như sau:
ADR ptr, đối tượng
Không cần cho một nhãn hiệu: BASIC thực hiện các tính toán sử dụng giá trị hiện tại của P%.
Thay vì sử dụng hiệu máy tính, chương trình cũng có thể truy cập dữ liệu của nó sử dụng cơ sở-thân giải quyết. Trong kế hoạch này, một thanh ghi được chọn để lưu trữ các địa chỉ cơ sở dữ liệu của chương trình. Nó được khởi tạo trong một số cách độc lập vị vào lúc bắt đầu của chương trình, sau đó tất cả các truy cập dữ liệu là tương đối này. Chế độ đăng ký địa chỉ-offset của ARM trong LDR và STR hiện điều này một cách khá đơn giản của việc truy cập dữ liệu.
Tại sao phấn đấu cho vị trí độc lập? Trong một hệ thống ARM điển hình, các chương trình bạn viết sẽ được nạp vào bộ nhớ RAM, và có thể phải chia sẻ bộ nhớ RAM với các chương trình khác. Hệ điều hành sẽ tìm một địa điểm thích hợp cho các chương trình và tải nó ở đó. Như 'có thể là bất cứ nơi nào trong phạm vi bộ nhớ có sẵn, chương trình của bạn có thể làm cho không có giả định về vị trí của thói quen nội bộ và dữ liệu của nó. Như vậy tất cả các tài liệu tham khảo phải được liên quan đến máy tính. Chính vì lý do này mà các chi nhánh sử dụng hiệu thay vì địa chỉ tuyệt đối, và lắp ráp cung cấp
LDR <đích>, <expression>
hình thức của LDR và STR để tự động tạo các địa chỉ máy tính tương đối.
Nhiều bộ vi xử lý (đặc biệt là những người lớn tuổi, tám-bit) làm cho nó không thể viết mã vị trí độc lập bởi vì các hướng dẫn không phù hợp và kiến trúc. ARM làm cho nó tương đối dễ dàng, và bạn nên tận dụng lợi thế này.
Tất nhiên, có những ràng buộc để có một số tài liệu tham khảo tuyệt đối trong chương trình. Bạn có thể phải gọi thủ tục con bên ngoài trong các hệ điều hành. Cách thông thường để làm điều này là sử dụng một SWI , mà mặc nhiên gọi địa chỉ tuyệt đối và 0.000.008. Con trỏ đưa vào chương trình bằng thói quen nhớ phân bổ sẽ được tuyệt đối, nhưng khi họ nằm ngoài chương trình, điều này không quan trọng.Những điều cần tránh là tham chiếu tuyệt đối với các đối tượng bên trong.
trình tự
Những chỉ đảm bảo một đề cập đến. Như chúng ta đã ngụ ý, hướng dẫn thực hiện tuần tự ARM trừ bộ vi xử lý được chỉ thị phải làm khác. Chuỗi các bài tập cao cấp:
LET a = b + c
LET d = bc
LET d = bc
sẽ được thực hiện bởi một chuỗi tương tự như các hướng dẫn ARM:
ADD ra, rb, rc
thứ SUB, rb, rc
thứ SUB, rb, rc
IF-loại điều kiện
Hãy xem xét các báo cáo BASIC:
NẾU a = b THEN count = count + 1
Các bản đồ này khá tốt vào chuỗi ARM sau đây:
CMP ra, rb
đếm ADDEQ, đếm, # 1
đếm ADDEQ, đếm, # 1
Trong này và các ví dụ, chúng tôi sẽ giả định toán hạng là vào sổ đăng ký để tránh nhiều LDR s và STR s. Trong thực tế, bạn có thể tìm thấy một số tiền nhất định của bộ vi xử lý-to-bộ nhớ chuyển giao đã được thực hiện.
Khả năng của ARM để thực hiện bất kỳ hướng dẫn điều kiện cho phép chúng tôi thực hiện chuyển đổi đơn giản từ BASIC. Tương tự như vậy, một đơn giản IF .. THEN ... ELSE như này
NẾU val <0 THEN ký = -1 dấu ELSE = 1
dẫn đến ARM tương đương:
TEQ val, # 0
dấu MVNMI, # 0
MOVPL dấu, # 1
dấu MVNMI, # 0
MOVPL dấu, # 1
Các điều kiện đối diện ( MI và PL ) trên hai hướng dẫn làm cho họ loại trừ lẫn nhau (ví dụ như một và chỉ một trong số họ sẽ được thực hiện sau khi TEQ ), tương ứng với cùng một tài sản trong THEN và ELSEphần của tuyên bố BASIC.
Thường có một giới hạn thực tế về nhiều hướng dẫn có thể được thực thi có điều kiện trong một chuỗi. Ví dụ, một trong các hướng dẫn điều kiện tự nó có thể ảnh hưởng đến những lá cờ, vì vậy các điều kiện ban đầu không còn nắm giữ. Một đa từ Thanh sẽ cần để ảnh hưởng đến các cờ carry, vì vậy hoạt động này không thể được thực hiện bằng điều kiện thực hiện. Các giải pháp (và chỉ có phương pháp mà hầu hết các bộ vi xử lý có thể sử dụng) là để có điều kiện chi nhánh qua hướng dẫn không mong muốn.
Dưới đây là một ví dụ về một add hai từ mà thực hiện chỉ khi R0 = R1:
CMP R0, R1
BNE noAdd
ADDS LO1, LO1, LôA2
ADC hi1, hi1, hi2
.noAdd ...
BNE noAdd
ADDS LO1, LO1, LôA2
ADC hi1, hi1, hi2
.noAdd ...
Chú ý rằng các điều kiện sử dụng tại các chi nhánh là đối diện với theo đó Thanh sẽ được thực hiện. Dưới đây là bản dịch chung của các báo cáo BASIC:
NẾU cond THEN ELSE sequence1 tuyên bố sequence2
; phiên bản 'ARM'
; Lấy <cond>
B <KHÔNG cond> seq2; Nếu <cond> thất bại sau đó nhảy đến ELSE
sequence1; Nếu không làm THEN phần
...
BAL endSeq2; Bỏ qua ELSE phần
.seq2
sequence2; này được thi hành nếu <cond> không
...
.endSeq2
tuyên bố; Các đường dẫn lại tham gia ở đây
; phiên bản 'ARM'
; Lấy <cond>
B <KHÔNG cond> seq2; Nếu <cond> thất bại sau đó nhảy đến ELSE
sequence1; Nếu không làm THEN phần
...
BAL endSeq2; Bỏ qua ELSE phần
.seq2
sequence2; này được thi hành nếu <cond> không
...
.endSeq2
tuyên bố; Các đường dẫn lại tham gia ở đây
Vào cuối của THEN trình tự là một chi nhánh không điều kiện để bỏ qua những ELSE phần. Hai con đường nối lại tại endSeq2 .
Đó là thông tin để xem xét các timings tương đối của các hướng dẫn bỏ qua và những điều kiện thi hành. Giả sử chuỗi có điều kiện bao gồm nhóm X một hướng dẫn. Bảng dưới đây cung cấp cho các timings trong chu kỳ cho các trường hợp khi chúng được thực hiện và không được thực hiện, sử dụng từng phương pháp:
Chi nhánh
|
có điều kiện
| |
Thực thi
| s + XX | xs |
không được thực hiện
| 2n + s | xs |
Trong trường hợp các hướng dẫn được thực hiện, phương pháp chi nhánh đã thực hiện các chi nhánh chưa thực hiện, cho một chu kỳ phụ. Điều này cho chúng ta kết quả chứ không phải dự đoán được rằng nếu chuỗi có điều kiện là chỉ có một hướng dẫn, phương pháp thực hiện có điều kiện nên luôn luôn được sử dụng.
Khi trình tự được bỏ qua vì điều kiện là sai, phương pháp chi nhánh có 2n + s, tương đương với 5s chu kỳ. Phương pháp chi nhánh có điều kiện mất một chu kỳ s cho mỗi lệnh bỏ thi. Vì vậy, nếu có bốn hoặc ít hơn chỉ dẫn, ít nhất một chu kỳ đã lưu sử dụng hướng dẫn điều kiện. Tất nhiên, cho dù điều này làm cho các chương trình thực thi nhanh hơn bất kỳ phụ thuộc vào tỷ lệ giữa thất bại và thành công của điều kiện.
Trước khi chúng tôi rời khỏi NẾU công trình xây dựng kiểu, chúng tôi trình bày một cách tốt đẹp của việc thực hiện các điều kiện như:
NẾU a = 1 hoặc a = 5 OR a = 12 ...
Nó sử dụng điều kiện thực hiện:
TEQ một, # 1
TEQNE một, # 5
TEQNE một, # 12
BNE thất bại
TEQNE một, # 5
TEQNE một, # 12
BNE thất bại
Nếu lần đầu tiên TEQ cho một EQ kết quả (tức là một = 1), hai hướng dẫn tiếp theo là bỏ qua và trình tự kết thúc với tình trạng cờ mong muốn. Nếu một <> 1, tiếp theo TEQ được thực thi, và một lần nữa nếu điều này mang lại một EQ kết quả, hướng dẫn cuối cùng được bỏ qua. Nếu không phải của hai thành công, kết quả của toàn bộ chuỗi đến từ thức TEQ .
Một tài sản hữu ích của TEQ là nó có thể được sử dụng để kiểm tra các dấu hiệu và zero-Ness của một đăng ký trong một lệnh. Vì vậy, một quyết định ba chiều có thể được thực hiện theo liệu một toán hạng là ít hơn số không, bằng không, hoặc lớn hơn không:
TEQ R0, # 0
BMI neg
BEQ zero
BPL cộng
BMI neg
BEQ zero
BPL cộng
Trong ví dụ này, một trong ba nhãn được tăng lên theo các dấu hiệu của R0. Lưu ý rằng các hướng dẫn cuối cùng có thể là một chi nhánh không điều kiện, như PL phải đúng sự thật nếu chúng tôi đã nhận rằng đến nay.
Trình tự thực hiện dưới sự phân công BASIC a = ABS (a) sử dụng hướng dẫn điều kiện:
TEQ một, # 0
RSBMI một, # 0; nếu a <0 thì a = 0-một
RSBMI một, # 0; nếu a <0 thì a = 0-một
Như bạn có thể nhận ra, hướng dẫn điều kiện cho phép các biểu hiện thanh lịch của nhiều loại đơn giản của NẾU ... xây dựng.
chi nhánh đa chiều
Thông thường, một chương trình cần có một trong một vài hành động có thể, tùy thuộc vào một giá trị hoặc một điều kiện. Có hai cách chính để thực hiện một chi nhánh như vậy, tùy thuộc vào các bài kiểm tra thực hiện.
Nếu các hành động được thực hiện phụ thuộc vào một trong số ít các điều kiện cụ thể, nó được thực hiện tốt nhất sử dụng so sánh, ngành rõ ràng. Ví dụ, giả sử chúng ta muốn lấy một trong ba hành động tùy thuộc vào việc các nhân vật trong byte thấp nhất của R0 là một lá thư, một chữ số hoặc một số nhân vật khác. Giả sử rằng các nhân vật thiết lập được sử dụng là ASCII, thì điều này có thể đạt được như sau:
CMP R0, # ASC "0";? Ít hơn các chữ số thấp nhất
BCC doOther; Vâng, vì vậy phải là 'khác'
CMP R0, # ASC "9"; có phải là một chữ số?
BLS doDigit; Có
CMP R0, # ASC " A "; giữa chữ số và chữ hoa?
BCC doOther; có nên 'khác'
CMP R0, # ASC" Z "; có trường hợp trên?
BLS doLetter; có
CMP R0, # ASC" a "; giữa chữ hoa và chữ ?
BLT doOther; Có nên 'khác'
CMP R0, # ASC "z"; Hạ trường hợp?
BHI doOther; Không, như vậy 'khác'
.doLetter
...
B nextChar; Quy trình nhân vật tiếp theo
.doDigit
...
B nextChar; Quy trình nhân vật tiếp theo
.doOther
...
.nextChar
...
BCC doOther; Vâng, vì vậy phải là 'khác'
CMP R0, # ASC "9"; có phải là một chữ số?
BLS doDigit; Có
CMP R0, # ASC " A "; giữa chữ số và chữ hoa?
BCC doOther; có nên 'khác'
CMP R0, # ASC" Z "; có trường hợp trên?
BLS doLetter; có
CMP R0, # ASC" a "; giữa chữ hoa và chữ ?
BLT doOther; Có nên 'khác'
CMP R0, # ASC "z"; Hạ trường hợp?
BHI doOther; Không, như vậy 'khác'
.doLetter
...
B nextChar; Quy trình nhân vật tiếp theo
.doDigit
...
B nextChar; Quy trình nhân vật tiếp theo
.doOther
...
.nextChar
...
Lưu ý rằng khi các nhân vật đã được sắp xếp ra, dòng điều khiển đã được chia thành ba tuyến đường có thể. Để làm cho chương trình dễ dàng hơn để làm theo, ba nhãn đích đến nên được gần nhau. Nó rất có thể là sau mỗi thói quen đã làm công việc của nó, là ba con đường sẽ hội tụ lại thành một chủ đề duy nhất. Để làm rõ điều này, mỗi định kỳ được chấm dứt bởi một chi nhánh nhận xét với điểm hẹn.
Một yêu cầu chung là chi nhánh để một thói quen nhất định theo một loạt các giá trị. Điều này được đặc trưng bởi BASIC của ON ... PROC và Case báo cáo. Ví dụ:
ON x PROCadd, PROCdelete, PROCamend, PROClist ELSE PROCerror
Theo dù x có giá trị 1, 2, 3 hoặc 4, một trong bốn thủ tục niêm yết được thực thi. Các ELSE ... phần cho phép x chứa giá trị ngoài phạm vi dự kiến.
Một cách để thực hiện một ON ... kiểu cấu trúc trong ngôn ngữ lắp ráp được sử dụng so sánh lặp đi lặp lại:
CMP lựa chọn, # 1; Kiểm tra đối với giới hạn dưới
BCC lỗi; Hạ, do lỗi
BEQ thêm; lựa chọn = 1 nên thêm
CMP lựa chọn, # 3; Kiểm tra 2 hoặc 3
BLT xóa; lựa chọn = 2 để xóa
BEQ sửa đổi; lựa chọn = 3 để sửa đổi
CMP lựa chọn, # 4; Kiểm tra đối với giới hạn trên
danh sách BEQ; Nếu lựa chọn = 4 danh sách các lỗi khác
.error
...
BCC lỗi; Hạ, do lỗi
BEQ thêm; lựa chọn = 1 nên thêm
CMP lựa chọn, # 3; Kiểm tra 2 hoặc 3
BLT xóa; lựa chọn = 2 để xóa
BEQ sửa đổi; lựa chọn = 3 để sửa đổi
CMP lựa chọn, # 4; Kiểm tra đối với giới hạn trên
danh sách BEQ; Nếu lựa chọn = 4 danh sách các lỗi khác
.error
...
Mặc dù kỹ thuật này là tốt cho các phạm vi nhỏ, nó trở nên lớn và chậm cho phạm vi rộng các lựa chọn . Một kỹ thuật tốt hơn trong trường hợp này nó sử dụng một bảng chi nhánh. Một danh sách các chi nhánh để các thói quen được lưu trữ gần các chương trình, và điều này được sử dụng để chi nhánh đến các thói quen thích hợp. Dưới đây là một thực hiện các ví dụ trước đây sử dụng kỹ thuật này.
Org DIM 200
lựa chọn = 0
t = 1
sp = 13
link = 14
REM Phạm vi của giá trị pháp lý
min = 1
max = 4
viết = 1
Newline = 3
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
; Multiway chi nhánh trong ARM lắp ráp
; lựa chọn có chứa mã, min..max của thói quen để gọi
; Nếu ra khỏi phạm vi, lỗi được gọi là
;
STMFD (sp), {t, link}!
lựa chọn SUBS, lựa chọn, #min; Choice <min?
BCC lỗi; Vâng, vậy lỗi
CMP lựa chọn, # max-min; Choice> max?
lỗi BHI; Vâng, vậy lỗi
ADR liên kết, trở lại; Set-up địa chỉ trả về
ADR t, bảng; Nhận địa chỉ của cơ sở bảng
ADD PC, t, lựa chọn, LSL # 2; Jump to bảng + lựa chọn * 4
;
.error
SWI viết
EQUS "lỗi Range"
EQUB 0
ALIGN
;
.return
SWI Newline
! LDMFD (sp), {t, PC}
;
;
; bảng chi nhánh để thói quen
.table
B add
B xóa
B sửa đổi
danh mục B
;
.add
SWI viết
EQUS "Thêm lệnh"
EQUB 0
ALIGN
MOV PC, liên kết
;
.delete
SWI viết
EQUS "xóa lệnh"
EQUB 0
ALIGN
MOV PC, liên kết
;
.amend
SWI viết
EQUS "Sửa đổi lệnh"
EQUB 0
ALIGN
MOV PC, liên kết
;
.list
SWI viết
EQUS "Danh sách lệnh"
EQUB 0
ALIGN
MOV PC, link
]
TIẾP
REPEAT
INPUT "Choice", A%
CALLorg
ĐẾN FALSE
lựa chọn = 0
t = 1
sp = 13
link = 14
REM Phạm vi của giá trị pháp lý
min = 1
max = 4
viết = 1
Newline = 3
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
; Multiway chi nhánh trong ARM lắp ráp
; lựa chọn có chứa mã, min..max của thói quen để gọi
; Nếu ra khỏi phạm vi, lỗi được gọi là
;
STMFD (sp), {t, link}!
lựa chọn SUBS, lựa chọn, #min; Choice <min?
BCC lỗi; Vâng, vậy lỗi
CMP lựa chọn, # max-min; Choice> max?
lỗi BHI; Vâng, vậy lỗi
ADR liên kết, trở lại; Set-up địa chỉ trả về
ADR t, bảng; Nhận địa chỉ của cơ sở bảng
ADD PC, t, lựa chọn, LSL # 2; Jump to bảng + lựa chọn * 4
;
.error
SWI viết
EQUS "lỗi Range"
EQUB 0
ALIGN
;
.return
SWI Newline
! LDMFD (sp), {t, PC}
;
;
; bảng chi nhánh để thói quen
.table
B add
B xóa
B sửa đổi
danh mục B
;
.add
SWI viết
EQUS "Thêm lệnh"
EQUB 0
ALIGN
MOV PC, liên kết
;
.delete
SWI viết
EQUS "xóa lệnh"
EQUB 0
ALIGN
MOV PC, liên kết
;
.amend
SWI viết
EQUS "Sửa đổi lệnh"
EQUB 0
ALIGN
MOV PC, liên kết
;
.list
SWI viết
EQUS "Danh sách lệnh"
EQUB 0
ALIGN
MOV PC, link
]
TIẾP
REPEAT
INPUT "Choice", A%
CALLorg
ĐẾN FALSE
Bốn dòng đầu tiên kiểm tra các dải giá trị trong sự lựa chọn , và gọi lỗi nếu nó nằm ngoài phạm vi phút đến tối đa . Điều quan trọng để làm điều này là, nếu một chi nhánh có thể được thực hiện một mục không hợp lệ trong bảng phân nhánh. Các thử nghiệm đầu tiên sử dụng SUBS thay vì CMP , vì vậy lựa chọn được điều chỉnh trong phạm vi từ 0 đến max-min thay vì phút đến tối đa .
Tiếp theo, các địa chỉ trả lại được đặt trong R14. Các thói quen thêm , xóa , vv trở lại, nếu như họ đã được gọi là sử dụng BL , tức là sử dụng một địa chỉ trở lại trong R14. Để làm điều này, chúng tôi sử dụngADR để đặt địa chỉ của các nhãn trở thành R14, việc này là nơi mà chúng tôi muốn tiếp tục thực hiện.
Tiếp theo ADR có được địa chỉ cơ sở của bảng nhảy trong thanh ghi của t . Cuối cùng, Thanh sẽ nhân lựa chọn bởi 4 (sử dụng hai ca trái) và thêm này bù đắp cho địa chỉ cơ sở của bảng. Kết quả của việc bổ sung được đặt trong chương trình truy cập. Điều này gây ra thực hiện để nhảy đến lệnh rẽ nhánh trong bảng đã được biểu hiện bằng sự lựa chọn . Từ đó, các thói quen thích hợp được gọi là, với địa chỉ trả lại vẫn còn trong R14.
Như chúng tôi đã đề cập trong phần mã vị trí độc lập, điều này có thể có vẻ một chút liên quan đến chỉ để nhảy đến một trong bốn địa điểm. Ghi dù rằng kỹ thuật này sẽ làm việc cho một số tùy ý các mục trong bảng, và sẽ làm việc tại bất cứ địa chỉ các chương trình được nạp.
Loops
Looping là rất quan trọng đối với bất kỳ chương trình không tầm thường. Nhiều vấn đề có giải pháp được thể hiện trong một thời trang lặp đi lặp lại. Có hai loại quan trọng của Looping xây dựng. Việc đầu tiên là vòng lặp trong khi, hoặc cho đến khi, một điều kiện nhất định được đáp ứng (ví dụ như REPEAT và WHILE vòng trong BASIC). Việc thứ hai là vòng lặp cho một số lần lặp lại (ví dụ CHO vòng). Trong thực tế, lớp thứ hai thực sự là một trường hợp đặc biệt của các vòng lặp có điều kiện chung, điều kiện được rằng vòng lặp đã lặp chính xác số lần.
Một đặc tính quan trọng của bất kỳ cấu trúc vòng lặp là nơi mà các bài kiểm tra về điều kiện vòng lặp được thực hiện. Trong BASIC REPEAT vòng, ví dụ, các bài kiểm tra được thực hiện tại tương ứng ĐẾN .Điều này có nghĩa rằng các hướng dẫn trong vòng lặp luôn luôn thực hiện ít nhất một lần. Hãy xem xét ví dụ này:
REPEAT
IF a> b THEN a = ab ELSE b = ba
đến khi a = b
IF a> b THEN a = ab ELSE b = ba
đến khi a = b
Đây là một cách đơn giản để tìm ước số chung lớn nhất (GCD) của một và b . Nếu một = b (và một <> 0) khi vòng lặp được nhập vào, kết quả là một vòng lặp vô hạn như trên phiên đầu tiên b = ba sẽ được thực thi, thiết b 0. Từ đó về sau, một = a-0 sẽ được thực hiện, sẽ không bao giờ làm một = b .
Các KHI vòng lặp kiểm tra điều kiện ở 'top', trước khi báo cáo của nó đã được thực hiện ở tất cả:
KHI a <> b
IF a> b THEN a = ab ELSE b = ba
endwhile
IF a> b THEN a = ab ELSE b = ba
endwhile
Lần này, nếu một = b , điều kiện ở phía trên sẽ thất bại, do đó vòng lặp sẽ không bao giờ được thực thi, để lại một = b = GCD ( một , b ).
Dưới đây là hai tương đương ARM của REPEAT và WHILE phiên bản lặp của thói quen GCD:
; Tìm GCD của ra, rb.
; Phiên bản có thể sai lầm sử dụng 'lặp lại' loop
.repeat
CMP ra, rb; REPEAT IF a> b
SUBGT ra, ra, rb; THEN a = ab
rb SUBLE, rb, ra; ELSE b = ba
CMP ra, rb; ĐẾN
BNE lặp lại; a = b
;
; Phiên bản có thể sai lầm sử dụng 'lặp lại' loop
.repeat
CMP ra, rb; REPEAT IF a> b
SUBGT ra, ra, rb; THEN a = ab
rb SUBLE, rb, ra; ELSE b = ba
CMP ra, rb; ĐẾN
BNE lặp lại; a = b
;
; Tìm GCD của ra, rb, sử dụng 'trong khi' loop
.while
CMP ra, rb; KHI a <> b
BNE endwhile
SUBGT ra, ra, rb; NẾU a> b THEN a = ab
SUBLE rb, rb, ra; ELSE b = ba
B trong khi; endwhile
.endwhile
.while
CMP ra, rb; KHI a <> b
BNE endwhile
SUBGT ra, ra, rb; NẾU a> b THEN a = ab
SUBLE rb, rb, ra; ELSE b = ba
B trong khi; endwhile
.endwhile
Chú ý rằng sự khác biệt giữa hai là KHI đòi hỏi một chi nhánh về phía trước để bỏ qua những hướng dẫn trong thân của vòng lặp. Đây không phải là một vấn đề đối với một nhà lắp ráp, trong đó có để đối phó với các tài liệu tham khảo về phía trước để được sử dụng bất kỳ ở tất cả. Trong một ngôn ngữ giải thích như BASIC, mặc dù, sự cần thiết phải quét qua một chương trình tìm kiếm một hợp endwhile là một cái gì đó của một gánh nặng, đó là lý do tại sao một số của BASIC không có cấu trúc như vậy.
Bởi vì cả hai chuỗi mã trên là bản dịch trực tiếp của các phiên bản cao cấp, họ là biểu hiện của những gì chúng ta có thể mong đợi một trình biên dịch tốt để sản xuất. Tuy nhiên, chúng ta tốt hơn bất kỳ trình biên dịch, và có thể tối ưu hóa cả hai chuỗi nhẹ một chút quan sát. Trong vòng đầu tiên, chúng tôi chi nhánh trở lại tới một chỉ dẫn mà chúng ta vừa thực hiện, lãng phí một chút thời gian. Trong trường hợp thứ hai, chúng ta có thể sử dụng các hướng dẫn điều kiện để loại bỏ các chi nhánh đầu tiên hoàn toàn. Dưới đây là các phiên bản mã hóa bằng tay:
; Có thể sai lầm phiên bản sử dụng 'lặp lại'
CMP ra, rb; REPEAT IF a> b
.repeat
SUBGT ra, ra, rb; THEN a = ab
rb SUBLE, rb, ra; ELSE b = ba
CMP ra, rb; ĐẾN
BNE lặp lại; a = b
;
CMP ra, rb; REPEAT IF a> b
.repeat
SUBGT ra, ra, rb; THEN a = ab
rb SUBLE, rb, ra; ELSE b = ba
CMP ra, rb; ĐẾN
BNE lặp lại; a = b
;
; Tìm GCD của ra, rb, sử dụng 'trong khi' loop
.while
CMP ra, rb; REPEAT
SUBGT ra, ra, rb; NẾU a> b THEN a = ab
SUBLT rb, rb, ra; ELSE IF một <bb = ba
BNE khi; ĐẾN a = b endwhile
.while
CMP ra, rb; REPEAT
SUBGT ra, ra, rb; NẾU a> b THEN a = ab
SUBLT rb, rb, ra; ELSE IF một <bb = ba
BNE khi; ĐẾN a = b endwhile
Bằng cách tối ưu hóa, chúng tôi đã chuyển đổi các KHI vòng lặp thành một REPEAT vòng lặp với một cơ thể hơi khác nhau.
Nói chung, một REPEAT cấu trúc kiểu được sử dụng khi chế biến trong 'cơ thể' của các vòng lặp sẽ cần ít nhất một lần, trong khi KHI vòng kiểu phải được sử dụng trong các tình huống mà các trường hợp 'null' là một khả năng riêng biệt. Ví dụ, thói quen xử lý chuỗi trong thông dịch BASIC phải đối phó với chuỗi số không dài, trong đó thường có nghĩa là một KHI cấu trúc lặp được sử dụng. (Xem ví dụ chuỗi xử lý sau này.)
Một trường hợp đặc biệt chung của REPEAT vòng lặp là vòng lặp vô hạn, thể hiện như:
Lặp lại
REM làm điều gì đó
đến khi FALSE
REM làm điều gì đó
đến khi FALSE
hoặc ARM lắp ráp:
.loop
; làm điều gì đó
BAL vòng lặp
; làm điều gì đó
BAL vòng lặp
Chương trình mà hiện hành vi này thường là những người tương tác đó lấy một lượng tùy ý các đầu vào từ người sử dụng. Một lần nữa các thông dịch BASIC là một ví dụ tốt. Các lối ra từ các chương trình như vậy thường là thông qua một số phương pháp 'cửa sau' (ví dụ như gọi một chương trình khác) chứ không phải là một số điều kiện được xác định rõ.
Từ CHO vòng là một trường hợp đặc biệt của các vòng chung, họ có thể được thể hiện trong các điều khoản của họ. Các CHO vòng lặp trong BBC BASIC trưng bày một REPEAT -like hành vi, trong đó kiểm tra để chấm dứt được thực hiện ở cuối, và nó thực hiện ít nhất một lần. Dưới đây là một điển hình cho vòng lặp và nó REPEAT tương đương:
REM Một điển hình cho vòng lặp
CHO ch = 32 ĐẾN 126
VDU ch
TIẾP ch
REM REPEAT vòng lặp tương đương
ch = 32
REPEAT
VDU ch
ch = ch + 1
ĐẾN ch> 126
CHO ch = 32 ĐẾN 126
VDU ch
TIẾP ch
REM REPEAT vòng lặp tương đương
ch = 32
REPEAT
VDU ch
ch = ch + 1
ĐẾN ch> 126
Việc chuyển nhượng ban đầu được đặt ngay trước REPEAT . Cơ thể của REPEAT cũng giống như đối với các CHO , với việc bổ sung các incrementing của ch ngay trước khi tình trạng này. Các điều kiện là ch là lớn hơn giới hạn được đưa ra trong CHO tuyên bố.
Chúng tôi có thể mã CHO vòng lặp trong ARM lắp ráp bằng cách làm việc từ REPEAT phiên bản lặp:
; In ký tự 32..126 sử dụng một vòng lặp CHO kiểu xây dựng
; R0 giữ nhân vật
MOV R0, # 32; Init nhân vật
.loop
SWI WriteC; In nó
ADD R0, R0, # 1; Increment nó
CMP R0, # 126 ; Kiểm tra giới hạn
vòng lặp BLE; vòng nếu không hoàn thành
;
; R0 giữ nhân vật
MOV R0, # 32; Init nhân vật
.loop
SWI WriteC; In nó
ADD R0, R0, # 1; Increment nó
CMP R0, # 126 ; Kiểm tra giới hạn
vòng lặp BLE; vòng nếu không hoàn thành
;
Rất thường xuyên, chúng tôi muốn làm một cái gì đó một số cố định của thời gian, có thể được thể hiện như một vòng lặp bắt đầu cho i = 1 TO n ... trong BASIC. Khi vòng như đang gặp phải trong lắp ráp, chúng ta có thể sử dụng thực tế là không có kết quả của nhóm một hướng dẫn có thể được thực hiện để thiết lập các cờ Z. Trong trường hợp này, việc cập nhật các biến vòng lặp và các bài kiểm tra cho chấm dứt có thể được kết hợp thành một hướng dẫn.
Ví dụ, để in mười ngôi sao trên màn hình:
CHO i = 1 TO 10
IN "*";
TIẾP i
IN "*";
TIẾP i
có thể được tái mã hóa trong các hình thức:
; In mười ngôi sao trên màn hình
; R0 giữ nhân vật sao, R1 đếm
MOV R0, # ASC "*"; Init char để in
MOV R1, # 10; Init đếm
.loop
SWI WriteC; In một ngôi sao
SUBS R1, R1 , # 1; Tiếp theo
BNE vòng lặp
;
; R0 giữ nhân vật sao, R1 đếm
MOV R0, # ASC "*"; Init char để in
MOV R1, # 10; Init đếm
.loop
SWI WriteC; In một ngôi sao
SUBS R1, R1 , # 1; Tiếp theo
BNE vòng lặp
;
Các SUBS sẽ đặt cờ Z sau khi lần thứ mười vòng quanh (tức là khi R1 đạt 0), vì vậy chúng tôi không cần phải làm một bài kiểm tra rõ ràng.
Tất nhiên, nếu giá trị hiện tại của biến vòng lặp đã được sử dụng trong cơ thể của vòng lặp, phương pháp này có thể không được sử dụng (trừ khi các vòng lặp là các hình thức CHO i = n ĐẾN 1 Bước -1 ...) như chúng tôi đang đếm ngược từ giới hạn, thay vì lên từ 1.
Một số ngôn ngữ cấp cao cung cấp phương tiện lặp đi lặp lại một vòng trước khi kết thúc hoặc rút lui khỏi vòng lặp hiện sớm. Hai vòng lặp 'extras' được đặc trưng bởi sự tiếp tục và phá vỡ câu lệnh trong ngôn ngữ C. Tiếp tục gây ra bước nhảy được thực hiện chỉ sau khi tuyên bố cuối trong hiện CHO , WHILE hoặc REPEAT kiểu vòng lặp, và phá vỡ hiện một lệnh nhảy tới phát biểu đầu tiên sau vòng lặp hiện tại.
Bởi vì tiếp tục và phá vỡ nguyên nhân dòng điều khiển phân kỳ từ hành động dự kiến của một vòng lặp, họ có thể làm cho chương trình càng khó theo dõi và hiểu được. Họ thường chỉ được sử dụng để 'thoát' khỏi một số điều kiện không thường xuyên hoặc bị lỗi. Cả hai cấu trúc có thể được thực hiện trong ARM sử dụng các chi nhánh có điều kiện hoặc không điều kiện.
5.2 thủ tục con và thủ tục
Bây giờ chúng ta đã bao phủ các cấu trúc kiểm soát dòng chảy chính. Chương trình được viết bằng chỉ những cấu trúc sẽ là rất lớn và khó đọc. Trình tự, quyết định và vòng lặp cấu trúc giúp để sản xuất một giải pháp ra lệnh cho một vấn đề nhất định. Tuy nhiên, họ không đóng góp cho bộ phận của vấn đề thành các đơn vị nhỏ hơn, dễ quản lý hơn. Đây là nơi mà các chương trình con đi vào.
Thậm chí đơn giản nhất của các vấn đề đó là một trong nhiều khả năng sử dụng máy tính để giải quyết có thể được chia ra thành một bộ đơn giản, tiểu chương trình ngắn hơn. Những động cơ để thực hiện phân hủy này là nhiều. Con người chỉ có thể đưa vào rất nhiều thông tin cùng lúc. Trong điều kiện của chương trình, một trang của danh sách là một giới hạn hữu ích cho bao nhiêu một lập trình hợp lý có thể được dự kiến sẽ tiêu hóa trong một đi. Ngoài ra, bằng cách thực hiện các giải pháp cho một phần nhỏ của một vấn đề, bạn có thể được viết cùng một phần của một chương trình sau. Thật đáng ngạc nhiên bao nhiêu có thể được thực hiện thói quen sử dụng "thư viện hiện có, mà không cần phải tái phát minh ra bánh xe mỗi lần.
Các chủ đề của chương trình phân hủy và từ trên xuống, lập trình cấu trúc là xứng đáng với những cuốn sách ở bên phải của riêng của họ, và nó được khuyến cáo bạn nên tham khảo những điều này nếu bạn muốn viết chương trình tốt ngôn ngữ nào. Kỷ luật của lập trình cấu trúc thậm chí còn quan trọng hơn trong lắp ráp hơn, nói rằng, Pascal, bởi vì nó là dễ dàng hơn để viết mã xảo trá không thể đọc trong lắp ráp.
Một phân hủy tối thiểu của hầu hết các chương trình được thể hiện trong sơ đồ khối ở trang sau. Dữ liệu được đưa vào, xử lý một cách nào đó, sau đó kết quả đầu ra. Nếu bạn nghĩ về nó, hầu hết các chương trình sẽ là khá nhàm chán nếu họ phụ thuộc vào hoàn toàn không có tác nhân kích thích bên ngoài cho các kết quả của họ.
Một khi đầu vào, chế biến và sản lượng giai đoạn đã được xác định, công việc có thể bắt đầu vào giải quyết những phần riêng lẻ. Hầu như không thay đổi này sẽ liên quan đến phân hủy hơn nữa, cho đến khi cuối cùng một tập các thói quen sẽ thu được có thể được viết trực tiếp trong một số phù hợp nhỏ các hướng dẫn cơ bản.
Cách thức mà những thói quen được liên kết với nhau, và cách họ giao tiếp với nhau, là đối tượng của các phần tiếp theo.
Chi nhánh và liên kết
ARM BL giảng dạy là một chương trình con gọi điện thoại nguyên thủy. Nguyên thủy trong bối cảnh này có nghĩa là một hoạt động được thực hiện ở mức thấp nhất, không có chi tiết ẩn hơn.
Nhớ lại từ Chương Ba rằng BL gây ra một chi nhánh đến một địa chỉ nhất định, và lưu trữ các địa chỉ trở lại trong R14. Chúng tôi sẽ minh họa việc sử dụng BL gọi ba thói quen mà giải quyết một vấn đề rất đơn giản. Điều này có thể được thể hiện như sau: liên tục đọc một ký tự đơn từ bàn phím và nếu nó không phải là các ký tự NUL (mã ASCII 0), in ra số của 1 bit trong mã.
Để so sánh, các chương trình BASIC bên dưới giải quyết các vấn đề sử dụng chính xác các cấu trúc tương tự như các phiên bản ARM sau đây:
REPEAT ch = FNreadChar
NẾU ch <> 0 PROCoutput (FNprocess (ch))
ĐẾN ch = 0
END
REM **************************** ***************
DEF FNreadChar = GET
REM ****************************** *************
DEF FNprocess (ch)
LOCAL đếm
count = 0
REPEAT
count = count + ch MOD 2
ch = ch DIV 2
ĐẾN ch = 0
= count
REM ******* ************************************
DEF PROCoutput (num)
IN num
ENDPROC
NẾU ch <> 0 PROCoutput (FNprocess (ch))
ĐẾN ch = 0
END
REM **************************** ***************
DEF FNreadChar = GET
REM ****************************** *************
DEF FNprocess (ch)
LOCAL đếm
count = 0
REPEAT
count = count + ch MOD 2
ch = ch DIV 2
ĐẾN ch = 0
= count
REM ******* ************************************
DEF PROCoutput (num)
IN num
ENDPROC
Có bốn thực thể, ngăn cách bởi dòng dấu hoa thị. Ở phía trên là "chương trình chính. Đây là mức độ cao nhất và là tự trị: không có thói quen khác gọi chương trình. Ba phần tiếp theo là những thói quen mà các chương trình chính sử dụng để giải quyết vấn đề. Vì đây là một ví dụ khá tầm thường, không ai trong số các chương trình con gọi nào khác; tất cả chúng đều được làm từ các sản phẩm nguyên thủy.Thông thường (và đặc biệt là trong ngôn ngữ assembly mà nguyên thủy là chỉ đó), những thói quen "mức độ thứ hai" sẽ gọi những người thậm chí còn đơn giản hơn, và như vậy.
Dưới đây là danh sách các phiên bản ARM lắp ráp của chương trình:
DIM org 200
sp = 13
link = 14
REM số SWI
WriteC = 0
Newline = 3
ReadC = 4
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
; đọc ký tự và in số lượng 1 bit trong
; ASCII mã, miễn là các mã không phải là zero.
STMFD (sp), {link}!; Lưu địa chỉ trả lại
.repeat
BL readChar; Nhận một nhân vật trong R0
CMP R0, # 0; là nó bằng không?
LDMEQFD (sp), {PC};! Vâng, vì vậy trở về người gọi
quá trình BL; Lấy số trong R1
ra BL; in R1 là một chữ số
lặp lại B; làm điều đó một lần nữa
;
;
; readChar - Điều này trả về một nhân vật trong R0
; Tất cả các thanh ghi khác bảo quản
;
.readChar
SWI ReadC; Gọi hệ điều hành cho các đọc
MOV PC, liên kết; Return sử dụng R14
;
; quá trình - đếm số bit 1 trong R0 bit 0..7
; Nó trả về các kết quả trong R1
; Mở lối ra, R1 = count, R0 = 0, tất cả những người khác bảo quản
;
.process
vÀ R0, R0, # & FF; Zero, bit 8 ..31 của R0
MOV R1, # 0; Init bit đếm
.procLoop
MOVS R0, R0, LSR # 1; DIV 2 và nhận được MOD 2 trong carry
ADC R1, R1, # 0; Thêm carry để đếm
BNE procLoop; More làm
MOV PC, liên kết; Return với R1 = count
;
; đầu ra - in R1 là một chữ số
; Mở lối ra, R0 = R1 + "0", tất cả những người khác bảo quản
;
.output
ADD R0, R1, # ASC "0" Chuyển đổi R1 để ASCII trong R0
SWI WriteC; in chữ số
SWI Newline; Và một dòng
PC MOV, liên kết; Return
]
TIẾP
CALL org
sp = 13
link = 14
REM số SWI
WriteC = 0
Newline = 3
ReadC = 4
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
; đọc ký tự và in số lượng 1 bit trong
; ASCII mã, miễn là các mã không phải là zero.
STMFD (sp), {link}!; Lưu địa chỉ trả lại
.repeat
BL readChar; Nhận một nhân vật trong R0
CMP R0, # 0; là nó bằng không?
LDMEQFD (sp), {PC};! Vâng, vì vậy trở về người gọi
quá trình BL; Lấy số trong R1
ra BL; in R1 là một chữ số
lặp lại B; làm điều đó một lần nữa
;
;
; readChar - Điều này trả về một nhân vật trong R0
; Tất cả các thanh ghi khác bảo quản
;
.readChar
SWI ReadC; Gọi hệ điều hành cho các đọc
MOV PC, liên kết; Return sử dụng R14
;
; quá trình - đếm số bit 1 trong R0 bit 0..7
; Nó trả về các kết quả trong R1
; Mở lối ra, R1 = count, R0 = 0, tất cả những người khác bảo quản
;
.process
vÀ R0, R0, # & FF; Zero, bit 8 ..31 của R0
MOV R1, # 0; Init bit đếm
.procLoop
MOVS R0, R0, LSR # 1; DIV 2 và nhận được MOD 2 trong carry
ADC R1, R1, # 0; Thêm carry để đếm
BNE procLoop; More làm
MOV PC, liên kết; Return với R1 = count
;
; đầu ra - in R1 là một chữ số
; Mở lối ra, R0 = R1 + "0", tất cả những người khác bảo quản
;
.output
ADD R0, R1, # ASC "0" Chuyển đổi R1 để ASCII trong R0
SWI WriteC; in chữ số
SWI Newline; Và một dòng
PC MOV, liên kết; Return
]
TIẾP
CALL org
Bởi vì cách thức mà các chương trình bám sát các phiên bản BASIC, bạn không nên có nhiều khó khăn sau đó. Dưới đây là một số điểm cần lưu ý. Trong phiên bản BASIC, hai trong số những thủ tục con,quá trình và readChar , là những chức năng và in ấn là một thủ tục. Trong phiên bản ARM, không có sự phân biệt rõ ràng như vậy trong cách thói quen được gọi là. Tuy nhiên, thực tế là quá trình vàreadChar giá trị trả lại cho người gọi họ làm cho họ tương đương với chức năng, trong khi quá trình, trả về không có giá trị sử dụng cho người gọi, là một thủ tục tương đương.
Vào lúc bắt đầu của mỗi định kỳ là một mô tả ngắn về những gì nó làm và làm thế nào nó ảnh hưởng đến các thanh ghi. Tài liệu như vậy là tối thiểu mà bạn cần cung cấp khi viết một thói quen, do đó vấn đề chẳng hạn như đăng ký được thay đổi bất ngờ được dễ dàng hơn để theo dõi. Để làm điều này khi các thói quen hệ điều hành được sử dụng (ví dụ như các WriteC SWI cuộc gọi), bạn cần phải biết làm thế nào những thói quen ảnh hưởng đến các thanh ghi. Thông tin này sẽ được cung cấp trong tài liệu của hệ thống. Để bây giờ, chúng tôi giả định rằng không có đăng ký được thay đổi, ngoại trừ những người trong đó kết quả được trả về, ví dụ như R0 trong SWI ReadC .
Trong các thói quen quá trình chúng tôi sử dụng các khả năng (a) thiết lập các cờ C từ kết quả của việc chuyển đổi một <RHS> toán hạng, và (b) bảo toàn tình trạng cờ Z qua ADC bằng cách không xác định Stùy chọn. Điều này cho phép chúng tôi viết một phiên bản ba-giảng dạy hiệu quả của các vòng lặp BASIC.
Những thói quen ra giả định rằng các mã trong những biểu tượng chữ số chạy liên tục kế nhau từ 0, 1, ... 9. Sử dụng giả thiết này, nó là một vấn đề đơn giản để chuyển đổi các số nhị phân 1..8 (nhớ & 00 sẽ không bao giờ có 1 bit của nó tính) vào mã in tương đương. Theo mã ASCII trưng bày các tài sản tiếp giáp mong muốn, và gần như đã được sử dụng để đại diện nhân vật, giả định là một trong những an toàn.
Như không có thói quen thay đổi đăng ký liên kết, R14, tất cả họ đều trở lại sử dụng một động thái đơn giản từ các liên kết đăng ký với máy PC. Chúng tôi không bận tâm để sử dụng MOVS để khôi phục lại những lá cờ quá, như họ không được dự kiến vào chương trình chính để được bảo tồn.
Nếu một chương trình con gọi một số khác sử dụng BL , sau đó đăng ký liên kết sẽ được ghi đè bằng các địa chỉ trả lại cho cuộc gọi sau này. Để cho các thói quen trước đó để trở về, nó phải giữ gìn R14 trước khi gọi các thói quen thứ hai. Là chương trình con rất thường xuyên gọi thói quen khác (tức là được 'lồng'), đến một độ sâu tùy ý, một cách nào đó là cần thiết trong tiết kiệm số lượng địa chỉ trở lại. Cách phổ biến nhất để làm điều này là để lưu các địa chỉ trên stack.
Đoạn chương trình dưới đây cho thấy cách thức đăng ký liên kết có thể được lưu vào mục đến một thói quen, và phục hồi trực tiếp vào máy tính ở lối ra. Sử dụng kỹ thuật này, đăng ký bất kỳ khác mà phải được bảo quản theo các thói quen có thể được lưu và phục hồi trong các hướng dẫn tương tự:
;
; SubEg. Đây là một ví dụ về cách sử dụng ngăn xếp để lưu
; địa chỉ trở lại của một chương trình con. Ngoài ra, R0, R1
và R2 đang được bảo tồn.
;
.subEg
STMFD (sp), {R0-R2, link};! Lưu liên kết và R0-R2
...; Do một số chế biến
...
LDMFD (sp)! , {R0-R2, pc} ^; tải PC, cờ và R0-R2
;
; SubEg. Đây là một ví dụ về cách sử dụng ngăn xếp để lưu
; địa chỉ trở lại của một chương trình con. Ngoài ra, R0, R1
và R2 đang được bảo tồn.
;
.subEg
STMFD (sp), {R0-R2, link};! Lưu liên kết và R0-R2
...; Do một số chế biến
...
LDMFD (sp)! , {R0-R2, pc} ^; tải PC, cờ và R0-R2
;
Các hình thức tiêu chuẩn của LDM và STM được sử dụng, có nghĩa là ngăn xếp là một 'đầy đủ, giảm dần "một. Viết lại được kích hoạt trên con trỏ ngăn xếp, vì nó hầu như luôn luôn sẽ cho xếp chồng các hoạt động, và khi máy tính được nạp từ ngăn xếp những lá cờ được khôi phục quá, do ^ trong hướng dẫn.
Lưu ý rằng nếu các 'thói quen' chỉ được gọi là SWI những người thân, sau đó không có nhu cầu để lưu sổ đăng ký liên kết, R14, trên stack. Mặc dù SWI lưu các máy tính và các lá cờ ở R14, nó là phiên bản chế độ giám sát của các thanh ghi này được sử dụng, và một của người dùng vẫn còn nguyên vẹn.
qua tham số
Khi giá trị được truyền đến một thói quen, chúng được gọi là các thông số, hay tranh luận, của các thói quen. Một thói quen thực hiện một số nhiệm vụ chung. Khi được cung cấp một tập hợp các đối số, nó thực hiện một hành động cụ thể hơn (nó được tham số, nếu bạn muốn) và các công việc thực hiện nó thường là như nhau cho một tập hợp các đối số. Khi một thói quen trả về một hoặc nhiều giá trị cho người gọi của nó, những giá trị này được biết đến như là kết quả của thói quen.
Thuật ngữ "chương trình con" thường được áp dụng cho một hoạt động nguyên thủy như chi nhánh và liên kết, cho phép một phần của mã được gọi là sau đó trở về từ. Khi một phương thức hoàn toàn xác định qua các thông số được kết hợp với cơ chế chương trình con cơ bản, chúng ta thường gọi đây là một thủ tục. Ví dụ, đầu ra trong ví dụ trên là một thủ tục mà phải mất một số từ 0 đến 9 trong R1 và in các chữ số tương ứng để này. Khi một thủ tục được gọi là để có được các kết quả trả về, nó được gọi là một hàm.
Bạn có thể đã nghe các thủ tục điều khoản và chức năng liên quan đến ngôn ngữ cấp cao. Khái niệm này là giá trị ngang nhau trong lắp ráp, và khi làm thủ tục và chức năng của một ngôn ngữ cấp cao được biên dịch (tức là chuyển đổi sang mã máy hoặc lắp ráp) họ chỉ sử dụng các cơ chế qua chương trình con cộng với số nguyên thủy mà chúng tôi mô tả trong phần này.
Trong chương trình ví dụ của phần trước, phiên bản BASIC sử dụng các biến toàn cầu như các tham số và kết quả, và phiên bản lắp ráp sử dụng đăng ký. Thông thường, các ngôn ngữ cấp cao cung cấp một cách để truyền các thông số an toàn hơn so với sử dụng các biến toàn cầu. Việc sử dụng như là toàn là không mong muốn bởi vì (a) người gọi và callee phải biết tên của biến đang được sử dụng và (b) các biến toàn cầu dễ bị tham nhũng bởi thói quen mà không làm 'nhận ra' họ đang được sử dụng ở những nơi khác trong chương trình.
Sử dụng đăng ký chỉ là một trong những cách mà các lập luận và kết quả có thể được thông qua giữa người gọi và callee. Các phương pháp khác bao gồm sử dụng vùng bộ nhớ cố định và ngăn xếp. Mỗi phương pháp có ưu điểm và nhược điểm riêng của nó. Chúng được mô tả trong các phần tiếp theo.
Đăng ký thông số
Trên một máy như ARM, sử dụng sổ đăng ký cho các thông tin liên lạc của lập luận và kết quả là sự lựa chọn rõ ràng. Đăng ký là khá phong phú (13 còn lại sau khi máy tính, liên kết và con trỏ ngăn xếp đã được đặt trước) và tiếp cận với họ là nhanh chóng. Hãy nhớ rằng trước khi ARM có thể thực hiện bất kỳ hướng dẫn xử lý dữ liệu, các toán hạng phải được nạp vào sổ đăng ký. Nó làm cho cảm giác sau đó để đảm bảo rằng chúng đã được đặt ra khi các thói quen được gọi.
Các thói quen hệ điều hành mà chúng tôi sử dụng trong các ví dụ sử dụng sổ đăng ký cho tham số. Nói chung, sổ đăng ký mà không được sử dụng để vượt qua kết quả lại được bảo quản trong các thói quen, tức là giá trị của họ không thay đổi gì khi điều khiển đi lại cho người gọi. Đây là một chính sách mà bạn nên xem xét sử dụng khi viết thói quen của riêng bạn. Nếu thủ tục tự bảo tồn và khôi phục sổ đăng ký, không có nhu cầu cho người gọi để làm như vậy mỗi khi nó sử dụng thường xuyên.
Hạn chế chính của các thông số đăng ký là họ chỉ có thể thuận tiện được sử dụng để giữ các đối tượng lên đến kích thước của một từ - 32-bit hoặc bốn byte. Điều này là tốt khi các dữ liệu gồm các ký tự đơn (như là kết quả của SWI ReadC ) và số nguyên. Tuy nhiên, các đối tượng lớn hơn như các xâu kí tự hoặc mảng các số không thể sử dụng đăng ký trực tiếp.
thông số tham khảo
Để khắc phục những vấn đề qua các đối tượng lớn, chúng tôi nhờ đến một hình thức hơi khác nhau của tham số. Cho đến bây giờ, chúng tôi đã giả định rằng nội dung của một thanh ghi chứa giá trị của ký tự hoặc số nguyên để được thông qua hoặc trả lại. Ví dụ, khi chúng ta sử dụng các thói quen gọi là quá trình trong ví dụ trước đó, R0 giữ giá trị của nhân vật để được xử lý và xuất cảnh R1 chứa giá trị của các số của số một bit. Không ngạc nhiên, phương pháp này được gọi là gọi theo giá trị.
Nếu thay vì lưu trữ các đối tượng chính nó trong một đăng ký, chúng tôi lưu trữ địa chỉ của đối tượng, những hạn chế kích thước của việc sử dụng sổ đăng ký để vượt qua các giá trị biến mất. Ví dụ, giả sử một thói quen yêu cầu tên của một tập tin để xử lý. Nó rõ ràng là không thực tế để vượt qua một chuỗi tùy tiện lâu sử dụng sổ đăng ký, vì vậy chúng tôi vượt qua địa chỉ của nơi mà các chuỗi được lưu trữ trong bộ nhớ thay.
Ví dụ dưới đây cho thấy làm thế nào một thói quen gọi wrchS có thể được viết và gọi. WrchS mất địa chỉ của một chuỗi trong R1, và chiều dài của chuỗi trong R2. Nó in chuỗi sử dụng SWI WriteC .
Lưu ý rằng các chương trình thử nghiệm có được địa chỉ một cách độc lập vị, sử dụng ADR . Các hành động đầu tiên của wrchS là để lưu R0 và đăng ký liên kết (có chứa địa chỉ trở về) vào ngăn xếp. Việc sử dụng ngăn xếp để tổ chức các dữ liệu đã được đề cập trong ChapterThree, và chúng ta sẽ có nhiều điều để nói về chúng sau này. Chúng tôi tiết kiệm R0 vì các đặc điểm kỹ thuật trong các ý kiến khẳng định rằng tất cả các đăng ký trừ R1 và R2 đang được bảo tồn. Vì chúng ta cần phải sử dụng R0 để gọi SWI WriteC , nội dung của nó phải được lưu.
Các vòng lặp chính của thói quen là các KHI đa dạng, với các thử nghiệm ở đầu trang. Điều này cho phép nó để đối phó với các chiều dài nhỏ hơn hoặc bằng số không. Các SUBS có tác dụng kép của giảm số chiều dài của một và thiết lập các cờ cho các điều kiện chấm dứt. Một LDRB được sử dụng để có được những nhân vật từ bộ nhớ, và sau đánh chỉ được sử dụng để tự động cập nhật các địa chỉ trong R1.
DIM org 200
sp = 13
link = 14
cr = 13: lf = 10
WriteC = 0
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
;
; Ví dụ hiển thị việc sử dụng wrchS
;
.testWrchS
STMFD (sp)! {link}; Lưu địa chỉ trả về
ADR R1, chuỗi; Nhận địa chỉ của chuỗi
MOV R2, # strEnd-string; tải chuỗi dài
BL wrchS; In nó
LDMFD (sp), {PC};! Return
;
.string
EQUS "Test chuỗi "; chuỗi được in
EQUB cr
EQUB lf
.strEnd
;
;
; chương trình con để in một chuỗi giải quyết bằng R1
; R2 chứa số byte trong chuỗi
; Mở lối ra, R1 chỉ để byte sau khi chuỗi
; R2 chứa -1
; Tất cả các thanh ghi khác bảo quản
.wrchS
STMFD (sp) !, {R0, link}; Lưu R0 và trở về địa chỉ
.wrchsLp
SUBS R2, R2, # 1;? Kết thúc chuỗi
LDMMIFD (sp) !, {R0 , PC}; có nên thoát
LDRB R0, [R1], # 1; Nhận một char và inc R1
SWI WriteC; In này nhân vật
B wrchsLp; Tiếp theo char
]
TIẾP
CALL testWrchS
sp = 13
link = 14
cr = 13: lf = 10
WriteC = 0
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
;
; Ví dụ hiển thị việc sử dụng wrchS
;
.testWrchS
STMFD (sp)! {link}; Lưu địa chỉ trả về
ADR R1, chuỗi; Nhận địa chỉ của chuỗi
MOV R2, # strEnd-string; tải chuỗi dài
BL wrchS; In nó
LDMFD (sp), {PC};! Return
;
.string
EQUS "Test chuỗi "; chuỗi được in
EQUB cr
EQUB lf
.strEnd
;
;
; chương trình con để in một chuỗi giải quyết bằng R1
; R2 chứa số byte trong chuỗi
; Mở lối ra, R1 chỉ để byte sau khi chuỗi
; R2 chứa -1
; Tất cả các thanh ghi khác bảo quản
.wrchS
STMFD (sp) !, {R0, link}; Lưu R0 và trở về địa chỉ
.wrchsLp
SUBS R2, R2, # 1;? Kết thúc chuỗi
LDMMIFD (sp) !, {R0 , PC}; có nên thoát
LDRB R0, [R1], # 1; Nhận một char và inc R1
SWI WriteC; In này nhân vật
B wrchsLp; Tiếp theo char
]
TIẾP
CALL testWrchS
Khi LDMMI được thực hiện, chúng tôi khôi phục R0 và trả lại cho người gọi, sử dụng một chỉ dẫn duy nhất. Nếu chúng tôi đã không được lưu trữ vào liên kết trên stack (như chúng ta đã làm trong hướng dẫn đầu tiên), thêm một MOV pc, liên kết sẽ được yêu cầu trả lại.
Gọi theo tham chiếu, hoặc gọi theo địa chỉ là một thuật ngữ được sử dụng khi các thông số được thông qua sử dụng địa chỉ của họ thay vì giá trị của chúng. Khi ngôn ngữ cấp cao sử dụng gọi theo tham chiếu (ví dụ var thông số trong Pascal), thường có một động cơ vượt ra ngoài thực tế có đăng ký không thể được sử dụng để lưu trữ các giá trị. Thông số tham khảo được sử dụng để cho phép các thói quen gọi là thay đổi các đối tượng có địa chỉ được thông qua. Trong thực tế, một số tài liệu tham khảo có thể được sử dụng để vượt qua kết quả một trở lại, và địa chỉ của kết quả được truyền tới thói quen trong một đăng ký.
Để minh họa việc sử dụng các kết quả tham khảo, chúng tôi trình bày dưới đây một thói quen gọi là đọc . Này được thông qua địa chỉ của một khu vực bộ nhớ trong R1. Một chuỗi các ký tự được đọc từ bàn phím bằng SWI ReadC , và được lưu trữ tại địa chỉ trên. Chiều dài của chuỗi đọc được trả về trong R0.
DIM org 100, đệm 256
WriteC = 0
ReadC = 4
xuống dòng = 3
cr = & 0D
sp = 13
link = 14
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
;
; đọc. Đọc một chuỗi từ bàn phím vào bộ nhớ
; giải quyết bằng R1. Các chuỗi được kết thúc bằng ký tự
; & 0D (vận chuyển trở lại) Trên lối R0 chứa chiều dài
; chuỗi, bao gồm cả CR
; Tất cả các thanh ghi khác được bảo lưu
;
.readS
STMFD (sp) !, {link}; Lưu lại địa chỉ
MOV R2, # 0; Init chiều dài
.readSlp
SWI ReadC; Nhận char trong R0
TEQ R0, #cr; có phải vận chuyển trở lại?
LỢN WriteC; Echo nhân vật nếu không
STRB R0, [R1, R2]; Store char
Thanh R2 , R2, # 1; Increment đếm
BNE readSlp; Nếu không CR, vòng lặp
SWI Newline; Echo xuống dòng
MOV R0, R2; Return đếm trong R0 cho USR
LDMFD (sp) !, {PC}; Return
]
TIẾP
B% = đệm
IN "string:";
len% = USR đọc
IN "Chiều dài là"; len%
IN "string là" $ đệm
WriteC = 0
ReadC = 4
xuống dòng = 3
cr = & 0D
sp = 13
link = 14
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
;
; đọc. Đọc một chuỗi từ bàn phím vào bộ nhớ
; giải quyết bằng R1. Các chuỗi được kết thúc bằng ký tự
; & 0D (vận chuyển trở lại) Trên lối R0 chứa chiều dài
; chuỗi, bao gồm cả CR
; Tất cả các thanh ghi khác được bảo lưu
;
.readS
STMFD (sp) !, {link}; Lưu lại địa chỉ
MOV R2, # 0; Init chiều dài
.readSlp
SWI ReadC; Nhận char trong R0
TEQ R0, #cr; có phải vận chuyển trở lại?
LỢN WriteC; Echo nhân vật nếu không
STRB R0, [R1, R2]; Store char
Thanh R2 , R2, # 1; Increment đếm
BNE readSlp; Nếu không CR, vòng lặp
SWI Newline; Echo xuống dòng
MOV R0, R2; Return đếm trong R0 cho USR
LDMFD (sp) !, {PC}; Return
]
TIẾP
B% = đệm
IN "string:";
len% = USR đọc
IN "Chiều dài là"; len%
IN "string là" $ đệm
Lần này, một REPEAT vòng lặp kiểu được sử dụng bởi vì chuỗi sẽ luôn luôn có ít nhất một nhân vật, trở về vận chuyển. Tất nhiên, một thói quen như thế này sẽ không phải là rất thiết thực để sử dụng: không có kiểm tra cho một chiều dài chuỗi tối đa; không có hành động trên các phím đặc biệt như DELETE hoặc ESCAPE được lấy. Nó hiện, tuy nhiên, cho thấy làm thế nào một số tài liệu tham khảo có thể được sử dụng để vượt qua địa chỉ của một biến mà là để được cập nhật bởi các thói quen.
khối tham số
Một khối tham số, hoặc khối điều khiển, là liên quan chặt chẽ đến các thông số tham khảo. Khi chúng tôi vượt qua một khối tham số để một thói quen, chúng tôi cung cấp cho nó địa chỉ của một khu vực của bộ nhớ, trong đó nó có thể tìm thấy một hoặc nhiều tham số. Ví dụ, giả sử chúng ta đã viết một thói quen để tiết kiệm diện tích bộ nhớ như là một tập tin có tên trên ổ đĩa. Một số thông số sẽ được yêu cầu:
- Đặt tên của các tập tin trên đĩa
- địa chỉ của dữ liệu bắt đầu
- địa chỉ kết thúc (hoặc chiều dài) của dữ liệu
- Tải địa chỉ của dữ liệu
- địa chỉ thi (trong trường hợp nó là một chương trình)
- Attributes (đọc, viết, vv)
Bây giờ, tất cả các mặt hàng này có thể được thông qua vào sổ đăng ký. Nếu chúng ta giả định tên được thông qua địa chỉ và có một số nhân vật khoanh vùng trên kết thúc, sáu thanh ghi sẽ được yêu cầu.Ngoài ra, các thông tin có thể được thông qua trong một khối tham số, địa chỉ bắt đầu của định được thông qua trong một đăng ký duy nhất. Các tập tin tiết kiệm thông thường có thể truy cập vào các thành phần của khối sử dụng, ví dụ
LDR [cơ sở, # bù đắp]
nơi cơ sở là đăng ký sử dụng để vượt qua địa chỉ bắt đầu, và bù đắp là địa chỉ của những từ bạn muốn liên quan đến cơ sở .
Là địa chỉ của khối tham số được truyền cho các thói quen, các thông số có thể thay đổi cũng như đọc. Như vậy khối tham số được hiệu quả tham khảo các thông số trong đó có thể được sử dụng để trả về thông tin ngoài đi qua nó. Ví dụ, khối tham số thiết lập cho một hoạt động tải đĩa có thể mục của nó được cập nhật từ các dữ liệu lưu trữ cho các tập tin trong danh mục đĩa (địa chỉ tải, chiều dài vv)
khối tham số có lẽ ít hữu ích trên máy với bộ đăng ký hào phóng như ARM hơn trên bộ xử lý mà ít được ưu đãi, ví dụ như vi tính 8-bit như 6502. Tuy nhiên, bạn nên nhớ lợi thế chỉ có một đăng ký bị cần thiết để vượt qua một vài thông số, và sẵn sàng để sử dụng kỹ thuật này nếu thích hợp.
thông số ngăn xếp
Kỹ thuật truyền thông số cuối cùng mà chúng ta sẽ mô tả sử dụng ngăn xếp để lưu trữ các đối số và kết quả. Trong chương ba, chúng tôi mô tả các LDM và STM hướng dẫn, mà việc sử dụng chính là đối phó với một cấu trúc ngăn xếp loại. Thông tin được đẩy vào một ngăn xếp sử dụng STM và kéo từ nó bằng cách sử LDM . Chúng ta đã thấy làm thế nào những hướng dẫn được sử dụng để giữ địa chỉ trở lại và ghi khác.
Để vượt qua các thông số trên stack, người gọi phải đẩy họ ngay trước khi gọi các thói quen. Nó cũng phải nhường chỗ cho bất kỳ kết quả mà nó hy vọng sẽ được trở lại trên stack. Ví dụ dưới đây gọi một thói quen mà hy vọng sẽ tìm thấy hai đối số trên stack, và trả về một kết quả duy nhất. Tất cả các mặt hàng được cho là chiếm một từ duy nhất.
;
; StackEg. Điều này cho thấy cách thức ngăn xếp có thể được sử dụng
, để vượt qua các đối số và nhận được kết quả từ một chồng.
; Trước khi nhập cảnh, hai đối số được đẩy, và xuất cảnh một
; kết quả duy nhất thay thế chúng. ;
.stackEg
STMFD (sp), {R0, R1};! Lưu các đối số
BL stackSub; Gọi các thói quen
LDMFD (sp), {} R0;! Lấy kết quả
sp Thanh, sp, # 8; 'Thua' các đối số
...
...
.stackSub
; StackEg. Điều này cho thấy cách thức ngăn xếp có thể được sử dụng
, để vượt qua các đối số và nhận được kết quả từ một chồng.
; Trước khi nhập cảnh, hai đối số được đẩy, và xuất cảnh một
; kết quả duy nhất thay thế chúng. ;
.stackEg
STMFD (sp), {R0, R1};! Lưu các đối số
BL stackSub; Gọi các thói quen
LDMFD (sp), {} R0;! Lấy kết quả
sp Thanh, sp, # 8; 'Thua' các đối số
...
...
.stackSub
LDMFD (sp), {R4, R5};! Lấy đối số
...; Do một số chế biến
...
STMFD (sp), {R2};! Lưu kết quả
MOV pc, liên kết; Return
...; Do một số chế biến
...
STMFD (sp), {R2};! Lưu kết quả
MOV pc, liên kết; Return
Nhìn vào mã này, bạn có thể nghĩ đến bản thân "những gì một sự lãng phí thời gian." Ngay sau khi một thói quen đẩy một giá trị, người kia kéo lại. Có vẻ như nhiều hơn nữa hợp lý để chỉ cần vượt qua các giá trị trong thanh ghi ở nơi đầu tiên. Thông báo, mặc dù, rằng khi stackSub được gọi, đăng ký sử dụng để thiết lập các ngăn xếp là khác nhau từ những người mà được nạp bên trong các thói quen. Đây là một trong những lợi thế của các thông số xếp chồng lên nhau: tất cả người gọi và callee cần phải biết là kích thước, số lượng và thứ tự của các tham số, không (rõ ràng), nơi chúng được lưu trữ.
Trong thực tế, nó là rất hiếm để tìm thấy chồng đang được sử dụng cho truyền thông số của chương trình hợp ngữ trong sạch, vì nó là đơn giản để phân bổ ghi cụ thể. Trường hợp đề án chồng thấy sử dụng nhiều hơn là trong thủ tục ngôn ngữ cấp cao biên soạn. Một số ngôn ngữ như C, cho phép các lập trình viên cho rằng các đối số cho một thủ tục có thể được truy cập trong bộ nhớ vị trí tiếp giáp. Hơn nữa, nhiều ngôn ngữ cấp cao cho phép các thủ tục đệ quy, tức là thủ tục mà tự gọi mình. Kể từ khi một bản sao của các thông số cần thiết cho mỗi lời gọi của một thủ tục, ngăn xếp là một nơi dễ thấy để lưu trữ chúng. Xem Acorn ARM Gọi chuẩn để giải thích về cách ngôn ngữ cấp cao sử dụng ngăn xếp.
Mặc dù chồng không thường được sử dụng để vượt qua các thông số trong chương trình hợp ngữ, chương trình con thường xuyên lưu đăng ký để bảo toàn giá trị của họ trên các cuộc gọi đến các thói quen.Chúng ta đã thấy cách đăng ký liên kết (và có thể những người khác) có thể được lưu sử dụng STM vào lúc bắt đầu của một thủ tục và phục hồi bằng LDM ở lối ra. Để minh họa thêm kỹ thuật này, các chương trình dưới đây cho thấy làm thế nào một thủ tục đệ quy có thể sử dụng ngăn xếp để lưu trữ các thông số trên các viện dẫn.
Các kỹ thuật được minh họa rất giống với các thông số chiều (và các biến địa phương) làm việc tại BBC BASIC. Tất cả các biến thực sự toàn cầu. Khi một thủ tục với dòng đầu tiên
DEF PROCeg (int%)
được gọi là sử dụng câu lệnh PROCeg (42) , sau đây sẽ xảy ra. Giá trị của int% được lưu trên stack. Sau đó int% được gán giá trị 42, và đây là những giá trị mà nó có trong suốt quy trình. Khi thủ tục trả về sử dụng ENDPROC , giá trị trước đó của int% được lấy từ các ngăn xếp, khôi phục lại giá trị cũ của nó.
Tương đương với ngôn ngữ lắp ráp của phương pháp này là phải vượt qua các thông số vào sổ đăng ký. Ngay trước khi một chương trình con được gọi, đăng ký mà phải được bảo tồn qua các cuộc gọi được đẩy, và sau đó đăng ký tham số được thiết lập. Khi thoát khỏi thói quen, sổ đăng ký lưu được lấy ra từ stack.
Có một số thói quen thường được dùng để minh họa cho đệ quy. Người sử dụng ở đây là phù hợp bởi vì đơn giản của nó; những vấn đề cần được giải quyết không nhận được trong cách thể hiện như thế nào đệ quy được sử dụng. Các dãy Fibonacci là một dãy số như sau:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
trong đó mỗi số là tổng của hai người tiền nhiệm của nó. Nó có thể được thể hiện bằng toán học về một số chức năng:
f (0) = 0
f (1) = 1
f (n) = f (n-2) + f (n-1)
trong đó f (n) là số thứ n trong dãy bắt đầu từ số không. Nó có thể dễ dàng được dịch sang một chức năng BASIC:
DEF FNfib (n) NẾU n <= 1 THEN ELSE = n = FNfib (n-2) + FNfib (n-1)
Để chuyển đổi này vào ARM lắp ráp, chúng tôi sẽ giả định rằng số n được thông qua tại R1 và fib kết quả ( n ) trả lại trong R0.
DIM org 200
link = 14
sp = 13
CHO qua = 0 TO 2 BƯỚC 2P% = org
[opt qua
; Fibonacci thông thường để trở fib (n)
; Mở nhập cảnh, R1 chứa n
; Mở lối ra, R0 chứa fib (n), R1 bảo quản, R2 tham nhũng
;
.fib
CMP R1, # 1; Xem nếu nó là một trường hợp dễ
MOVLE R0, R1; Có nên trả lại nó trong R0
MOVLE PC, liên kết; Và trở lại
! STMFD (sp), {link}; Lưu trở về địa chỉ
SUB R1, R1, # 2; Nhận fib (n-2) trong R0
BL fib
STMFD (sp), {R0};! Lưu nó trên đống
ADD R1, R1, # 1; Nhận fib (n-1 ) trong R0
BL fib
LDMFD (sp), {R2};! Kéo fib (n-2)
ADD R0, R0, R2; Thêm fib (n-2) và fib (n-1) trong R0
ADD R1, R1, # 1; Restore R1 để giá trị mục
! LDMFD (sp), {PC}; Return
]
NEXTFOR B% = 0 TO 25
IN "fib ("; B% ") là"; USR fib
TIẾP B%
link = 14
sp = 13
CHO qua = 0 TO 2 BƯỚC 2P% = org
[opt qua
; Fibonacci thông thường để trở fib (n)
; Mở nhập cảnh, R1 chứa n
; Mở lối ra, R0 chứa fib (n), R1 bảo quản, R2 tham nhũng
;
.fib
CMP R1, # 1; Xem nếu nó là một trường hợp dễ
MOVLE R0, R1; Có nên trả lại nó trong R0
MOVLE PC, liên kết; Và trở lại
! STMFD (sp), {link}; Lưu trở về địa chỉ
SUB R1, R1, # 2; Nhận fib (n-2) trong R0
BL fib
STMFD (sp), {R0};! Lưu nó trên đống
ADD R1, R1, # 1; Nhận fib (n-1 ) trong R0
BL fib
LDMFD (sp), {R2};! Kéo fib (n-2)
ADD R0, R0, R2; Thêm fib (n-2) và fib (n-1) trong R0
ADD R1, R1, # 1; Restore R1 để giá trị mục
! LDMFD (sp), {PC}; Return
]
NEXTFOR B% = 0 TO 25
IN "fib ("; B% ") là"; USR fib
TIẾP B%
Những thói quen không sử dụng stack trong cách chính xác giống như BBC BASIC, nhưng tiết kiệm của các kết quả trung gian trên stack cho phép fib được gọi là đệ quy trong cùng một cách. Lưu ý rằng điều quan trọng là khi trở R1 được bảo tồn là, tức là chứa n , như quy định trong các ý kiến. Đó là vì khi fib được gọi là đệ quy người gọi hy vọng R1 được để lại nguyên vẹn để nó có thể tính toán các giá trị tiếp theo một cách chính xác. Trong các trường hợp khi R1 = 0 hoặc 1, nhập nó được bảo tồn rõ ràng; trong các trường hợp khác, bằng cách quan sát R1 được thay đổi bằng -2, 1 và 1, tức là không có sự thay đổi ròng về giá trị của nó.
Bạn nên lưu ý rằng, giống như rất nhiều thói quen đó được thể hiện thanh lịch sử dụng đệ quy, chương trình Fibonacci này trở nên rất kém hiệu quả của thời gian và không gian ngăn xếp cho các giá trị khá nhỏ của n . Điều này là do số lượng cuộc gọi đệ quy thực hiện. (Đối với một bài tập bạn có thể vẽ một 'cây' của các cuộc gọi cho một số giá trị bắt đầu, nói 6.) Một giải pháp tốt hơn là một vòng lặp đếm. Điều này được thể hiện trong BASIC và ARM lắp ráp dưới đây.
DEF FNfib (n)
NẾU n <= 1 THEN = n
LOCAL f1, f2
f2 = 0: f1 = 1
CHO i = 0 TO n-2
f1 = f1 + f2
f2 = f1-f2
TIẾP i
= f1
DIM org 200
i = 2: REM việc đăng ký
f1 = 3
f2 = 4
sp = 13
link = 14
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
; fib - sử dụng lặp đi lặp lại thay vì đệ quy
; Mở nhập cảnh, R1 = n
; Mở xuất cảnh, R0 = fib (n)
;
.fib
CMP R1, # 1, thử nghiệm đầu tiên Trivial
MOVLE R0, R1
MOVLE PC, liên kết
STMFD (sp), {i, f1, f2, link};! Lưu đăng ký công việc và liên kết
MOV f1, # 1; Khởi tạo fib (n-1)
MOV f2, # 0; và fib (n-2)
SUB i, R1, # 2; Set-up đếm vòng lặp
.fibLp
ADD f1, f1, f2; Đỗ tính
SUB f2, f1, f2
SUBS i, i, # 1
BPL fibLp; Cho đến khi tôi đạt -1
MOV R0, f1; Return gây R0
LDMFD (sp), {i, f1, f2, PC};! Khôi phục và trở
TIẾP qua
CHO B% = 0 TO 25
IN "fib ("; B%; ") là"; USR fib
TIẾP B%
NẾU n <= 1 THEN = n
LOCAL f1, f2
f2 = 0: f1 = 1
CHO i = 0 TO n-2
f1 = f1 + f2
f2 = f1-f2
TIẾP i
= f1
DIM org 200
i = 2: REM việc đăng ký
f1 = 3
f2 = 4
sp = 13
link = 14
CHO qua = 0 TO 2 Bước 2
P% = org
[opt qua
; fib - sử dụng lặp đi lặp lại thay vì đệ quy
; Mở nhập cảnh, R1 = n
; Mở xuất cảnh, R0 = fib (n)
;
.fib
CMP R1, # 1, thử nghiệm đầu tiên Trivial
MOVLE R0, R1
MOVLE PC, liên kết
STMFD (sp), {i, f1, f2, link};! Lưu đăng ký công việc và liên kết
MOV f1, # 1; Khởi tạo fib (n-1)
MOV f2, # 0; và fib (n-2)
SUB i, R1, # 2; Set-up đếm vòng lặp
.fibLp
ADD f1, f1, f2; Đỗ tính
SUB f2, f1, f2
SUBS i, i, # 1
BPL fibLp; Cho đến khi tôi đạt -1
MOV R0, f1; Return gây R0
LDMFD (sp), {i, f1, f2, PC};! Khôi phục và trở
TIẾP qua
CHO B% = 0 TO 25
IN "fib ("; B%; ") là"; USR fib
TIẾP B%
Tóm lược
Động lực chính trong chương này đã được thể hiện như thế nào một số khái niệm quen thuộc của ngôn ngữ cấp cao có thể được áp dụng để lắp ráp. Hầu hết các cấu trúc điều khiển được dễ dàng thực hiện trong điều kiện của ngành, mặc dù những người phức tạp hơn (như đa chiều phân nhánh) có thể yêu cầu công việc nhẹ hơn. Điều này đặc biệt đúng nếu mã là để triển lãm các tài sản mong muốn của vị trí-độc lập cao.
Chúng tôi cũng đã thấy cách các tham số có thể được thông qua giữa thói quen - trong sổ đăng ký, trên stack, hoặc trong khối tham số. Sử dụng stack có lợi là cho phép đệ quy, nhưng ít hiệu quả hơn so với chuyển thông tin vào sổ đăng ký.
0 Comment:
Đăng nhận xét
Thank you for your comments!