Машина состояний

Последнее обновление: 02.07.2023

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

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

.data
    state byte 0        ; условное состояние
.code

state_machine proc
state_0:
    cmp state, 0    ; сраниваем состояние
    jne state_1
    add eax, ecx    ; состояние 0: складываем ECX и EAX и переключаемся на состояние 1
    inc state       ; изменяем состояние
    jmp exit
state_1:
    cmp state, 1
    jne state_2
    sub eax, ecx    ; состояние 1: вычитаем ECX из EAX и переключаемся на состояние 2
    inc state       ; изменяем состояние
    jmp exit
state_2: 
    cmp state, 2
    jne state_3
    imul eax, ecx   ; состояние 2: умножаем ECX на EAX и переключаемся на состояние 3
    inc state       ; изменяем состояние
    jmp exit
state_3:
    ; cmp state, 3  ; предположим, что это состояние будет в любом случае, но также можно сравнивать
    xor edx, edx    ; для деления расширяем нулями регистр EAX на EDX
    div ecx         ; состояние 3: делим EAX на ECX и переключаемся на состояние 0
    mov state, 0    ; изменяем состояние
exit: 
    ret
state_machine endp

main proc
    mov eax, 5
    mov ecx, 4
    call state_machine
    ret
main endp
end

Для данной программы предположим, что все возможные состояния описываются числами 0, 1, 2, 3, которые представляют определенную арифметическую операцию: сложение (0), вычитание (1), умножение (3) и деление (4). Для хранения состояния в программе определена переменная state. Для управления состоянием определена процедура state_machine, в которой мы сравниваем значение state с возможными состояниями. Если сравнение успешно, выполняем арифметическую операцию и изменяем состояние. Либо переходим к следующей метке для проверки нового состояния.

Через регистры EAX и ECX процедура state_machine получает значения:

mov eax, 5
mov ecx, 4
call state_machine

Таким образом, после выполнения процедуры будет выполняться операция сложения (так как переменная state на старте программы равна 0). В EAX окажется число 9, а переменная state получит значение 1.

Подобным образом мы могли бы несколько раз вызывать процедуру state_machine, передавая ей через RCX различные значения:

main proc
    mov eax, 5
    mov ecx, 4
    call state_machine  ; EAX = 9, state = 1

    mov ecx, 3
    call state_machine  ; EAX = 6, state = 2

    mov ecx, 4
    call state_machine  ; EAX = 24, state = 3

    mov ecx, 2
    call state_machine  ; EAX = 12, state = 0
    ret
main endp

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

.data
    state qword state_0     ; условное состояние
.code

option noscoped     ; делаем метки глобальными

state_machine proc
    jmp state
state_0:
    add eax, ecx    ; состояние 0: складываем ECX и EAX и переключаемся на состояние 1
    lea rcx, state_1
    mov state, rcx
    ret
state_1:
    sub eax, ecx    ; состояние 1: вычитаем ECX из EAX и переключаемся на состояние 2
    lea rcx, state_2
    mov state, rcx
    ret
state_2:
    imul eax, ecx   ; состояние 2: умножаем ECX на EAX и переключаемся на состояние 3
    lea rcx, state_3
    mov state, rcx
    ret
state_3:
    xor edx, edx    ; для деления расширяем нулями регистр EAX на EDX
    div ecx         ; состояние 3: делим EAX на ECX и переключаемся на состояние 0
    lea rcx, state_0
    mov state, rcx
    ret
state_machine endp

main proc
    mov eax, 5
    mov ecx, 4
    call state_machine  ; EAX = 9, state = state_1

    mov ecx, 3
    call state_machine  ; EAX = 6, state = state_2

    mov ecx, 4
    call state_machine  ; EAX = 24, state = state_3

    mov ecx, 2
    call state_machine  ; EAX = 12, state = state_0
    ret
main endp
end

Теперь состояния представляют метки state_N, на которые процецируются действия. Чтобы глобальная переменная state могла хранить адрес определенной метки внутри процедуры, перед процедурой указываем директиву option noscoped. В самой процедуре с помощью инструкции jmp сразу переходим к нужному состоянию:

jmp state
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850