2017-01-15 2 views
0

Мне нужно написать программу в сборке, которая читает число и печатает половину пирамиды чисел.Распечатайте половину пирамиды чисел в сборке

т.е. чтения 4

печати

1 
1 2 
1 2 3 
1 2 3 4 

Я понял, как читать номер, как использовать цикл для печати один символов в каждой строке, но я должен использовать внутренние циклы и Я не знаю, как инициализировать другой контур.

.286 
.model small 
.stack 1024h 
.data 

.code 

mov cx,5 
mov bx,5 

cosmin: 

mov dl,31h 
mov ah, 2h 
int 21h 

mov dl, 0Ah 
int 21h 

loop cosmin 

end 

Здесь я пытался сделать треугольник только для одного символа, но я не знаю, как для увеличения значения на каждую линию.

.286 
.model small 
.stack 1024h 
.data 

.code 

mov cx,5 
mov bx,5 

cosmin: 

mov dl,31h 
mov ah, 2h 
int 21h 

mov dl, 0Ah 
int 21h 

loop cosmin 

end 
+0

Должен ли второй источник каким-то образом отличаться? Он выглядит так же, как и для меня. – Ped7g

+1

У вас * есть * для использования внутренней петли для каждой строки. Вы можете либо сохранить CX (push/pop) из внешнего цикла, либо использовать какой-либо другой регистр и имитировать 'loop' с' dec' + 'jnz'. –

ответ

2

Я не знаю, как для увеличения значения на каждую линию.

Ну, за каждую строку делать inc where-the-value-is-stored (либо иметь его в каком-либо запасном регистре, либо в памяти, если у вас закончились запасные регистры).

Держите в голове или в резюме комментариев, каково ваше текущее распределение регистров, поэтому вы знаете, что является запасным или уже используется для чего-то.

Убедитесь, что вы выполняете требования внешних вызовов, так как вы можете легко выбрать другой регистр для своего собственного кода, но вы не можете изменить, например, int 21h, чтобы принять служебный номер в bh, поскольку он уже реализован ваш поставщик DOS, чтобы принять номер службы в ah. Поэтому либо избегайте использования ah, либо используйте шаблон сохранения/восстановления (см. Ниже).

Постарайтесь, чтобы простые простые вещи, такие как добавочное значение, равно inc. Сборка на самом деле неплохо в этом случае, если вы сохраняете четкое представление о том, что хотите в своей голове, с точки зрения очень простых числовых операций/шагов, вы обычно можете найти довольно простое и простое сочетание инструкций ASM, выполняющих именно это и не очень остальное. Довольно часто ничего больше.

Если у вас есть трудное время для сопоставления некоторого желания нескольким инструкциям по сборке простым способом, ваша задача высокого уровня, вероятно, не будет достаточно разбита на простые шаги, поэтому попробуйте разбить ее немного больше, а затем попробуйте еще раз найти некоторые короткий простой перевод в инструкции.


loop rel8 является одним из немногих более сложных инструкций x86, делая в основном это:

dec cx  (ecx in 32b mode, rcx in 64b mode) 
jnz rel8 

Но это не повлияет на флаги (dec + jnz происходит внутренне как одной специализированной вещи, не в буквальном смысле, как два оригинальные инструкции dec + jnz), и он искусственно замедляется на современных процессорах x86, чтобы помочь немного устаревшему SW, который использовал пустые loop $ циклы для создания «задержек» (это бесполезно, поскольку для этого SW оно слишком быстро, и оно «удаляет», иначе очень хороший код операции для будущего SW: /).

Таким образом, вы можете предпочесть настоящие две инструкции «dec cxjnz rel8» для реального программирования, это будет иметь лучшую производительность на современном процессоре x86.


В сборке Регистры CPU подобны «супер-глобальным», т.е. есть один cx на ядро ​​центрального процессора (внутренне это не так для современных x86, но это то, как он выглядит с внешней стороны, с точки зрения программиста).

Так что если вам нужны два разных значения, например counter1 и counter2, вам нужно будет написать дополнительный дополнительный код, сохраняя при этом нужное значение cx и загружая другое по необходимости.

Например два вложенных цикла осуществляется loop:

mov cx,10 
outer_loop: 
    mov bx,cx ; preserve outer counter in bx 
    mov cx,5 
inner_loop: 
    ; some loop code 
    loop inner_loop 
    mov cx,bx ; restore outer counter 
    loop outer_loop 

Или, если вам не хватает запасных регистров, вы можете использовать стек, "наивное" путь:

mov cx,10 
outer_loop: 
    push cx  ; preserve outer counter 
    mov cx,5 
inner_loop: 
    ; some loop code 
    loop inner_loop 
    pop cx  ; restore outer counter 
    loop outer_loop 

(C++ компиляторы разрешат это по-разному, выделяя локальную переменную в пространстве стека, поэтому вместо push/pop он будет использовать одно и то же пятно памяти на [sp+x] или [bp-x] напрямую, экономя производительность, не корректируя sp с e очень полезно, например push/pop.))

Но если вы посмотрите на предыдущую часть моего ответа, вы сможете найти другой способ решения вложенных циклов с двумя счетчиками - без дополнительных инструкций сохранения/восстановления.


Но картина сохранения/восстановления значения в конкретном целевом регистре является то, что вы должны полностью понимать и быть в состоянии использовать во всех видах различных ситуаций (даже если это не требуется для вложенных циклов), для Например, если вы прочтете документацию о ah=2, int 21h, вы увидите, что она заботится только о ah и dl значениях (и изменяет al). Так, например, dh является «запасным».

Затем, если вы хотите выводить два символа: A и пространство, но вы все еще хотите, чтобы закончить с A в главном «переменной» (будет dl в следующем примере), вы можете сделать это:

init_part: 
    mov dx,' '*256 + 'A' ; dh = ' ', dl = 'A' 
    mov ah,2    ; output single char service 
    ; some other init code, etc.. 

inner_part_somewhere_later: 
    int 21h    ; output dl to screen (initially 'A') 
    xchg dl,dh    ; preserves old "dl" and loads "dh" into it (swaps them) 
    int 21h    ; output dh to screen (space) 
    xchg dl,dh    ; restores 'A' in dl 
    ; so *here* you can operate with 'dl' 
    ; as some inner_part loop "variable" 
    ; modifying it for another inner_part iteration 

Наконец, если у вас есть задача, подобная вашей, и решение не является очевидным, одним из этапов рассуждения может быть «откат», что вы хотите.

Вы знаете, что вы хотите вывод на экран (<NL> = новая строка):

1<NL> 
1 2<NL> 

Так себе представить, что это означает, что на нижнем уровне, в конце концов. Конечно, есть несколько способов его достижения (включая запись целых строк, подготовленных в буфере памяти, вместо одиночных символов), но если я буду придерживаться вашего одиночного выходного сигнала, этот желаемый вывод переводится в эту потребность:

для того, чтобы позвонить int 21h, ah=2 с dl набор для:
[49 (цифра 1), 13 (возврат каретки), 10 (строки), 49, 32 (пробел), 50 (цифра 2), 13, 10].

Это не выглядит «зацикленным», но если вы добавите больше строк, появится образец «цифра + пробел» для внутреннего цикла. Вы также можете «обмануть» бит и вывести одно бесполезное пространство после последней цифры, так как для обычного пользователя он будет «невидим». В этот момент вы должны быть в состоянии «вернуться назад» на этот высокий уровень дизайна:

char_per_line_count = 1 
ending_char_count = 2 
[lines_loop: 
    char = '1' 
    line_counter = char_per_line_count 
    [chars_loop: 
     int 21h,2 with char 
     int 21h,2 with space 
     loop to chars_loop while (--line_counter)] 
    int 21h,2 with 13 
    int 21h,2 with 10 
    ++char_per_line_count 
    loop to lines_loop while (char_per_line_count < ending_char_count)] 

Теперь вы можете попробовать запустить его несколько раз в вашей голове, чтобы проверить выход действительно то, что вы хотите.

Как только вы получите такой высокий уровень обзора, как вы можете достичь желаемого результата, вы можете начать искать способ, как хорошо реализовать определенные шаги.

Если вы понимаете каждую предыдущую часть этого ответа, я думаю, что этот алгоритм будет легко переписать в инструкции ASM. Просто оставляйте комментарии в коде перед определенной группой инструкций.

Затем, когда вы отлаживаетесь из-за некоторой ошибки, вы можете легко сравнить то, что код действительно делает с комментарием, что он должен был сделать, найти расхождение и исправить его.

Но все время главное, чтобы сравнить ваш код с тем, что конечный результат на экране определен, всякий раз, когда вы застреваете, сравните свой текущий вывод с желаемым, найдите какое-то несоответствие, оцените, какой из них выглядит самым простым для исправления , и попытайтесь его исправить. Если больше не найдено расхождений, вы как бы «сделали», хотя я бы настоятельно предложил еще раз взглянуть на ваш код, не упростится ли его, и если он корректно работает для угловых шкафов (например, " что произойдет, если пользователь вводит букву вместо цифры «).

Не важно иметь код, который правильно обрабатывает каждый угловой футляр, но вы должны знать, что произойдет в каждом случае, и решить, является ли это «достаточно хорошим» или нет (как правило, мусор в -> вывоз мусора "в порядке," мусор в -> сбой или повреждение данных "не круто," мусор в -> значимое исправление или сообщение об ошибке прохладно).

+0

Кстати, этот высокоуровневый алгоритм в вашем конкретном случае может быть изменен для полного удаления 'line_counter', и выполните управление циклом, используя значение« char »и другое условие цикла. В коде ASM, который приведет к торговле 'dec + jnz' для' cmp end_value + jnz' и с использованием одного регистра меньше (значит, у вас есть еще один запас для других значений). – Ped7g

3

Чтобы создать внутренний цикл с другим loop инструкции, вы должны сначала сохранить состояние внешнего контура, так как оба команды loop используют cx.
После завершения внутреннего цикла вы должны восстановить указанное состояние.
Несоблюдение этого требования приведет к аннулированию счетчика внешнего контура, в частности, он будет циклически работать навсегда.

Вы можете сохранить счетчик внешнего контура почти везде, где хотите, однако, если у вас есть запасной регистр, например bx, используйте его.
Если у вас нет такого регистра, вы всегда можете использовать стек (push и pop), но кроме того, что, помимо введения ограничения на сбалансирование стека при каждом условии прерывания цикла, он обращается к памяти и выполняет поиск внешний счетчик более неуклюжий.

loop не that great instruction, он является ограничительным и медленным.
Плюс за проблемный счет до лучше, поэтому я бы полностью избегал его в пользу счетчика, управляемого вручную.

;Assume SI holds the number n (>=1) of rows of the pyramid 

mov cx, 1     ;Outer counter (i) 

_rows_loop: 
mov bx, 1     ;Inner counter (j) 
__cols_loop: 

    ;Print number bx 

    inc bx    ;Increment inner counter 
    cmp bx, cx   ;If (j<i) keep looping 
jb _cols_loop 

;Print new-line 

inc cx    ;Increment outer counter 
cmp cx, si   ;If (i<n) keep looping 
jb _rows_loop 

Если вы бежите из запасных регистров, вы всегда можете использовать стек, но учтите, что это усложняет код цикла непосильным, как все, что вы должны нажать всегда быть уравновешено поп-музыки. Плюс он обращается к памяти.

В крайнем случае можно использовать 8-разрядные регистры, используя cl и ch для cx и bx в коде выше, будет работать со счетчиками, которые умещаются.


Я оставляю вам проблему поиска алгоритма для создания пирамиды.

+0

Как использовать регистр SI для чтения n? mov ah, 01h int 21h al al, 48 mov si, al –

+0

Большое спасибо за ваше объяснение. Я знаю, что мне нужно больше учиться. –

+0

@CosminBaciu Для чисел от 0 до 9 ваш подход правильный, за исключением того, что 'mov si, al' недействителен, используйте' xor ah, ah', 'mov si, ax' (или' movzx si, al', если можете предоставить более новые инструкции). –

Смежные вопросы