Ваш код поврежден, потому что вы всегда переходите на save: MOV CH, AL
каждую итерацию, поэтому он может работать, только если последняя заглавная буква также является последним символом всего ввода.
Один шаг с отладчиком для простого ввода, например ABc*
, чтобы увидеть, как все идет не так.
Также вы используете loop
, что похоже на dec cx/jnz
.Это не имеет смысла, потому что нет условия завершения на основе счетчика, и может потенциально повредить CH, если CL был нулевым.Вы даже не инициализируете CX в первую очередь!Инструкция loop
- не единственный способ зацикливания;это просто оптимизация глазка размера кода, которую вы можете использовать, когда удобно использовать CX в качестве счетчика цикла.В противном случае не используйте его.
Это упрощенная версия реализации Sep, в которой используется тот факт, что ввод гарантированно будет буквенным, поэтому мы действительно можем проверить верхний регистр так же легко, какc <= 'Z'
(после исключения терминатора '*'
).Нам не нужно беспокоиться о входных данных, таких как 12ABcd7_
или пробелах или символах новой строки, которые также имеют более низкие коды ASCII, чем алфавитный диапазон верхнего регистра.Ваша проверка cmp al,'Z'
/ ja
была правильной, просто у кода, к которому вы переходили, не было здравой логики.
Даже если вы действительно хотели строго проверить c >= 'A' && c <= 'Z'
, эта проверка диапазона можетбыть сделано с одной веткой, используя sub al,'A'
;cmp al,'Z'-'A'
;ja non_upper
вместо пары веток cmp / jcc.(Это изменяет оригинал, но если вы сохраните его в SI или что-то еще, вы можете позже восстановить его с помощью lea ax, [si+'A']
)
Вы также можете поместить условную ветвь внизу цикла для обоих циклов вместоjmp
внизу и if() break
внутри.Код Sep уже сделал это для первого цикла.
Я согласен с Sep, что иметь 2 цикла проще, чем проверять флаг каждый раз, когда вы находите заглавную букву (чтобы увидеть, является ли она первой заглавной или нет).
ORG 100h ; DOS .com is loaded with IP=100h, with CS=DS=ES=SS
; we don't actually do any absolute addressing so no real effect.
mov ah, 01h ; DOS.GetKeyboardCharacter
; AH=01 / int 21h doesn't modify AH so we only need this once
find_first_cap:
int 21h ; stdin -> AL
cmp al, '*' ; Found end of input marker ?
je Done ; if (c=='*') return; without print anything, we haven't found a capital yet
cmp al, 'Z'
ja find_first_cap
; fall through: AL <= 'Z' and we can assume it's a capital letter, not a digit or something.
mov dl, al ; For now it's the first
;mov dh, al ; AND the last capital
;mov ah, 01h ; DOS.GetKeyboardCharacter AH still = 01
;jmp loop2_entry ; we can let the first iteration set DH
Loop2: ; do {
cmp al, 'Z' ; assume all c <= 'Z' is a capital alphabetic character
ja loop2_entry
mov dh, al ; This is the latest capital
loop2_entry:
int 21h ; stdin -> AL
cmp al, '*'
jne Loop2 ; }while(c != '*');
Show: mov ah, 02h ; DOS.DisplayCharacter
int 21h ; AL -> stdout
mov dl, dh
; mov ah, 02h ; DOS.DisplayCharacter
int 21h ; AL -> stdout
Done: mov ax, 4C00h ; DOS.TerminateWithReturnCode
int 21h
На данный момент это, возможно, не проще, но более оптимизировано, особенно для размера кода.Это имеет место, когда я пишу что-нибудь, потому что это забавная часть.: P
Наличие взятой ветви внутри цикла для случая, не являющегося заглавной, возможно, ухудшает производительность.(В современном коде для P6-совместимого процессора вы, вероятно, использовали бы cmovbe esi, eax
вместо условного перехода, потому что условный ход равен точно , что вы хотите.)
Пропуск mov ah, XX
до int 21h
, потому что он все еще установлен, не делает вашу программу более удобочитаемой, но это безопасно, если вы тщательно проверяете документы для каждого вызова, чтобы убедиться, что они неничего не вернуть в АХ.