문자열(대소문자 알파벳)을 입력하고 가장 많이 사용된 알파벳을 대문자로 출력해주는 코드를 작성하면 됩니다
이때 A, a를 따로 구분 짓지 않고 동일하게 봅니다 여기서 만약 가장 많이 사용된 횟수가 동일한 알파벳이 존재한다면 ?를 출력하면 됩니다
예제 입력에 따른 출력을 보면 다음과 같습니다 우선 코드를 보기 전에 결론부터 말하면....
.....이게 조금 그런 게 제 환경에서 질문 올라오는 반례들도 다 돌려봐도 올바르게 나왔는데 런타임 에러가 나왔습니다
정확히는 모르지만 처음 런타임 에러가 나온건 .data영역에 문자열을 입력받아서 그렇지 않을까 싶어 .bss영역으로도 옮겨보고 이것저것 해봤는데
결국 C언어로 다른 분들이 문제 푼 코드를 보고 아... 스택에 넣으면 되겠구나 싶어서 바꿨는데!!

런타임 에러.. 물론 맞지는 않았지만 그래도 복기할 겸 다시 정리해보는 것도 좋을 것 같아서 써봅니다
global main
extern scanf
extern printf
section .text
find_big:
push rbp
mov rbp, rsp
sub rsp, 0x20
xor rax, rax
mov [rbp-0x8], rax ; index
mov [rbp-0x10], rax ; biggest let
mov [rbp-0x18], rax ; biggest let index
mov [rbp-0x20], rax ; check if there's two same case
find_loop:
mov rax, [rbp-0x8]
cmp rax, 0x1b ; loop until 0x1a
je check_same
mov rax, [alpha + rax * 8]
cmp rax, qword[rbp-0x10] ; cmp the number of letter
jl next_find ; if it less than the prev letter then jump to the next loop
je two_case
mov [rbp-0x10], rax ; case if it's big
mov rax, [rbp-0x8]
mov [rbp-0x18], rax
xor rax, rax
mov [rbp-0x20], rax
jmp next_find
two_case:
inc qword[rbp-0x20] ; if there's same number of letter than inc
next_find:
inc qword[rbp-0x8] ; increase index
jmp find_loop
check_same:
mov rax, [rbp-0x20]
cmp rax, 1
jge exit_none
mov rax, [rbp-0x18]
add rax, 0x40
jmp exit_func
exit_none:
mov rax, 0x3f
exit_func:
add rsp, 0x20
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 0xf4250
xor rax, rax
mov [rbp-0x8], rax ; index
mov [rbp-0x10], rax
mov rdi, input
lea rsi, [rbp-0xf4250]
call scanf
loop:
mov rax, [rbp-0x8]
mov al, byte[rbp-0xf4250 + rax]
test rax, rax
je print_res
cmp rax, 0x60
jl cap_let
sub rax, 0x60
jmp next_loop
cap_let:
sub rax, 0x40
next_loop:
inc qword[alpha + rax * 8]
inc qword[rbp-0x8]
jmp loop
print_res:
call find_big
mov rdi, output
mov rsi, rax
call printf
xor rax, rax
leave
ret
section .data
alpha: TIMES 0x1b dq 0
input: db "%s",00
output: db "%c",10,00
코드를 어떤 식을 작성해야될지 생각해보면 문자열을 입력받아야 하고 문자열들의 각 문자를 대소문자 구분하지 않고 등장하는 횟수를 카운팅 해야 합니다
그리고 가장 많이 카운팅 된 문자 중 동일한 문자가 있는지 확인하고 있다면 그거에 맞는 처리를 해줘야겠죠
그래서 해당 코드는 문자열을 입력받고 각 문자의 횟수를 카운팅 해주는 main함수가 있고 main함수 내부에서 find_big함수를 call 하게 됩니다 find_big함수는 가장 많이 카운팅 된 알파벳을 선별하고 또한 가장 많이 카운팅 된 알파벳이 단일인지 검사하게 됩니다
main:
push rbp
mov rbp, rsp
sub rsp, 0xf4250
xor rax, rax
mov [rbp-0x8], rax ; index
mov [rbp-0x10], rax
mov rdi, input
lea rsi, [rbp-0xf4250]
call scanf
main함수에서 사용할 스택 + 문자열 길이만큼 rsp를 빼주고 scanf함수로 입력을 받습니다
loop:
mov rax, [rbp-0x8]
mov al, byte[rbp-0xf4250 + rax]
test rax, rax
je print_res
cmp rax, 0x60
jl cap_let
sub rax, 0x60
jmp next_loop
cap_let:
sub rax, 0x40
next_loop:
inc qword[alpha + rax * 8]
inc qword[rbp-0x8]
jmp loop
scanf로 문자열을 입력받은 뒤에 loop를 돌게 되는데 여기서는 입력받는 문자열을 한 문자씩 받아들여 대문자인지, 소문자 인지 검사하게 됩니다(cmp rax, 0x60) 만약 0x60보다 작으면 cap_let으로 점프하게 되는데 대문자이면 점프하고 소문자이면 0x60을 빼준뒤 next_loop으로 점프하게 됩니다
대문자는 0x40을 소문자는 0x60을 빼주기 때문에 수의 범위가 1 ~ 26까지가 되어 대소문자 구분 없이 alpha배열을 증가시킴으로써 각 문자의 횟수를 카운팅 합니다
[rbp-0x8]은 검사한 다음 문자열의 index를 위해 1 증가시킨 뒤 loop로 다시 점프하게 됩니다
여담이지만 여기서 만약 문자열이 입력되었을 때 문자열 마지막에 다른 데이터가 들어가 있거나 어떤 데이터 값을 overwrite 하게 되면 그 데이터를 포함하여 0x00이 나올 때까지 검사를 수행하게 될 때 문제가 발생할 것을 보입니다
strlen을 사용해도 결국 널 바이트까지의 길이를 판별하는 거라 달라지는 건 없어 보이고.... scanf함수를 구현해보면 해결될 듯합니다
print_res:
call find_big
mov rdi, output
mov rsi, rax
call printf
xor rax, rax
leave
ret
문자열의 각 문자의 횟수 체크가 되면 print_res로 점프하게 됩니다 여기서는 바로 find_big함수로 점프하게 되고
find_big함수의 반환 값(rax)을 인자로 넣고 출력시켜 줍니다
find_big:
push rbp
mov rbp, rsp
sub rsp, 0x20
xor rax, rax
mov [rbp-0x8], rax ; index
mov [rbp-0x10], rax ; biggest let
mov [rbp-0x18], rax ; biggest let index
mov [rbp-0x20], rax ; check if there's two same case
find_loop:
mov rax, [rbp-0x8]
cmp rax, 0x1b ; loop until 0x1a
je check_same
mov rax, [alpha + rax * 8]
cmp rax, qword[rbp-0x10] ; cmp the number of letter
jl next_find ; if it less than the prev letter then jump to the next loop
je two_case
mov [rbp-0x10], rax ; case if it's big
mov rax, [rbp-0x8]
mov [rbp-0x18], rax
xor rax, rax
mov [rbp-0x20], rax
jmp next_find
two_case:
inc qword[rbp-0x20] ; if there's same number of letter than inc
next_find:
inc qword[rbp-0x8] ; increase index
jmp find_loop
check_same:
mov rax, [rbp-0x20]
cmp rax, 1
jge exit_none
mov rax, [rbp-0x18]
add rax, 0x40
jmp exit_func
exit_none:
mov rax, 0x3f
exit_func:
add rsp, 0x20
leave
ret
find_big함수에서는 가장 카운팅이 된 알파벳을 찾고 만약 가장 많이 카운팅 된 알파벳이 한 개 이상이면 '?'를 출력시키도록 해주어야 합니다
find_big:
push rbp
mov rbp, rsp
sub rsp, 0x20
xor rax, rax
mov [rbp-0x8], rax ; index
mov [rbp-0x10], rax ; biggest let
mov [rbp-0x18], rax ; biggest let index
mov [rbp-0x20], rax ; check if there's two same case
사용되는 레지스터에 대한 주석을 적어놨습니다 사실 제가 잘 안 읽혀서 적었음다
[rbp-0x8]은 alpha배열의 index를 검사하기 위해 1 증가됩니다
for(int i = 0; i < N; i++){
rax = alpha[i];
}
위의 for문에서 i의 역할을 [rbp-0x8]이 수행하게 됩니다
여기서 잠깐 짚고 넘어갈게 alpha에는 각 알파벳이 등장한 횟수를 저장하고 있습니다
이 값을 꺼내서 값을 확인하고 가장 크다면 index에 0x40을 더해주면 해당 알파벳의 대문자가 될 것입니다
그래서 [rbp-0x10]은 현재까지 검사한 index 중 가장 많은 횟수를 저장하고 있으며 [rbp-0x18]에는 그에 대한 index가 저장합니다
마지막으로 [rbp-0x20]에는 가장 많이 카운팅 된 알파벳이 몇 개인지 체크하게 됩니다 단일일 시에는 0이고 둘 이상이 되면 1 이상으로 증가하게 됩니다
find_loop:
mov rax, [rbp-0x8]
cmp rax, 0x1b ; loop until 0x1a
je check_same
mov rax, [alpha + rax * 8]
cmp rax, qword[rbp-0x10] ; cmp the number of letter
jl next_find ; if it less than the prev letter then jump to the next loop
je two_case
mov [rbp-0x10], rax ; case if it's big
mov rax, [rbp-0x8]
mov [rbp-0x18], rax
xor rax, rax
mov [rbp-0x20], rax
jmp next_find
two_case:
inc qword[rbp-0x20] ; if there's same number of letter than inc
next_find:
inc qword[rbp-0x8] ; increase index
jmp find_loop
가장 먼저 find_loop이 등장합니다 find_loop에서는 [rbp-0x10]과 현재 인덱스의 횟수를 검사합니다
그리고 현재 인덱스의 횟수가 작게 되면 next_find로 점프하여 [rbp-0x8]를 1 증가시킨 뒤 find_loop로 점프하게 됩니다
만약 같다면 two_case로 점프하게 되는데 여기서는 단순히 [rbp-0x20]을 증가시킵니다
만약 현재 인덱스의 저장된 횟수가 [rbp-0x10]보다 작지도 같지도 즉 더 크게 되면 현재 횟수를 [rbp-0x10]에 저장하고 현재 인덱스(rbp-0x8)를 [rbp-0x18]에 저장하고 [rbp-0x20]을 0으로 초기화시킵니다 그 후 next_find로 점프하여 다음 루프를 준비합니다
이 루프는 0x1a(26)만큼 수행하고 check_same으로 점프하게 됩니다
check_same:
mov rax, [rbp-0x20]
cmp rax, 1
jge exit_none
mov rax, [rbp-0x18]
add rax, 0x40
jmp exit_func
exit_none:
mov rax, 0x3f
exit_func:
add rsp, 0x20
leave
ret
check_same은 [rbp-0x20]을 검사함으로써 단일인지 다수인지 검사하게 됩니다
단일이면 해당 해당 인덱스를 0x40과 더해주어 대문자로 만들고 exit_func으로 가서 스택을 정리하고 함수를 종료시킵니다
만약 단일이 아니게 되면 exit_none으로 점프하여 rax에 '?'의 아스키코드인 0x3f를 넣어주고 함수를 종료시킵니다
print_res:
call find_big
mov rdi, output
mov rsi, rax
call printf
xor rax, rax
leave
ret
이후에 printf를 통해 find_big 함수로부터 반환된 값을 출력시키게 됩니다
** 마무리 생각해도 문자열 입력받을 때 문제인 것 같습니다..
'자료구조, 알고리즘 > x64 assembly' 카테고리의 다른 글
쌩 어셈블리로 두수 더하기 (0) | 2021.12.28 |
---|---|
백준 2675 문자열 반복 (0) | 2021.11.08 |
백준 25577번 숫자의 개수 (0) | 2021.10.31 |
백준 2741 N 찍기 (0) | 2021.10.30 |