
Assembler / P09
.pdf1
9. Косвенная адресация. Команда организации цикла.
9.1. Индексная адресация До сих пор мы писали программы, в которых обращались к отдельным
ячейкам памяти, т.е. в терминологии языков программирования высокого уровня работали с простыми переменными. Но очень полезно иметь возможность работать с данными, объединенными в некоторые совокупности.
Массив на уровне реализации — это множество ячеек одинакового размера, занимающих непрерывную область памяти. Адресом массива называется адрес первого элемента массива. На рис. 9.1. схематически показан массив из четырех слов. Адрес массива ds:408
ds:0408 |
|
ds:040A |
|
ds:040C |
|
ds:040E |
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 9.1. |
|
|
|
Посмотрим, как в процессоре реализовано обращение к элементам массива. Допустим, мы рассматриваем байтовый массив по адресу (смещению) 300.
Первый элемент (с номером нуль) расположен по адресу 300, второй элемент — по адресу 301 и т.д. С теми возможностями, которыми мы сейчас располагаем, мы можем записать в аккумулятор, допустим, четвертый элемент командой mov al,[303] Но, оказывается, то же самое можно сделать иначе:
mov si,3
mov al,[si+300]
В индексный регистр SI мы поместили номер элемента, а затем выбрали его по адресу 300+SI. Можно было записать эту команду иначе: mov al,300[si] с тем же эффектом. Вместо SI здесь можно было использовать DI и BX (и даже BP — для com-программ, которые мы создаем в отладчике — для них совпадают значения сегментных регистров, а вообще-то BP предназначено для стекового сегмента).
Значение SI можно вычислять в программе, и тем самым обращаться к разным элементам массива.
Пример. В массиве из 16 байт, расположенном с 200-го адреса, сосчитать количество элементов, величина которых больше значения –2.
mov cx,10h; Количество элементов — в CX
mov bx,0 ; Счетчик подходящих элементов — в BX mov si,0 ; SI — индекс массива
m:cmp byte ptr [si+200],-2 ; Очередной элемент больше –2 ? jng n ; Нет — на n
inc bx ; Да — увеличить счетчик
n:inc si ; Перейти к следующему элементу
dec cx ; Уменьшить счетчик повторений цикла
jnz m ; Если счетчик повторений отличен от нуля — на m
nop |
; В BX — количество подходящих элементов |
Использованный метод адресации носит название — индексный.
9.2. Команды организации циклов В только что разобранной программе легко выделить конструкцию для ор-
ганизации цикла с фиксированным числом повторений:
2
для k = 1 до N шаг 1 выполнить <тело цикла>
Ее эквивалент на языке Ассемблера mov cx,N
next:
<тело цикла> dec cx
jnz next
Последние две команды можно заменить одной командой loop next.
Управление циклом |
loop opr |
CX ← CX–1. |
|
|
Если CX 0, то IP ←IP + Data8 |
LOOP — петля |
|
Флаги не изменяются |
Обратите внимание, что счетчик повторений — обязательно регистр CX. Команда loop проверяет именно его содержимое.
Если цикл включен в программу, и количество повторений тела цикла предварительно вычисляется, то имеет смысл проверить, что CX отличен от нуля.
Задача. Пусть в программе, приведенной выше команда mov cx,10h заменена на mov cx,0. Сколько раз будет выполнено тело цикла?
Ответ: 65536.
Проверку на равенство содержимого CX нулю легко осуществить последовательностью двух команд:
cmp cx,0 je error
но можно для этой же цели воспользоваться одной командой jcxz.
Перейти по CX = 0 |
jcxz opr |
если CX = 0 , то IP IP + Data8 |
Jump if CX equal Zero |
|
Флаги не изменяются |
Команду ставят перед началом цикла. Метка opr, разумеется, находится вне цикла. Эту команду причисляют к командам условного перехода, хотя она заведомо выпадает из их ряда: jcxz проверяет содержимое регистра CX, а прочие команды — состояние флагов.
Имеются еще две команды организации циклов: LOOPZ/LOOPE opr и LOOPNZ/LOOPNE opr (через дробную черту указаны альтернативные мнемоники). В них непосредственно перед уменьшением CX проводится проверка флага ZF и в зависимости от его значения выполнение цикла продолжается до исчерпания CX или досрочно прекращается. Но эти команды редко используются. Поэтому их изложение опустим.
9.3. Косвенная адресация Настало время перечислить все методы адресации. Нами уже изучены мето-
ды:
регистровый: inc bx
непосредственный: mov ax,6
прямой: dec word ptr [200].
При использовании косвенной адресации используется один или два регистра, в которых хранятся некоторые слагаемые адреса. Дадим сразу общую схему:
3
BX |
|
|
|
|
|
[EA] BP |
||
|
|
|
|
|
|
SI |
|
Data8 |
|
|
|
|
|
DI |
Data16 |
||
|
|
|
|
|
|||
|
|
|
|
|
Здесь EA — Effective Address — эффективный (исполнительный) адрес. Фигурные скобки означают, что вычисляется один из перечисленных внутри них элементов (в том числе и никакой: на это указывает пробел). Возможны любые комбинации перечисленных элементов, за исключением двух: 1) [] , т.е. недопустимы квадратные скобки без содержимого, 2) [BP]. Если все-таки нужно использовать [BP], то для этого следует использовать адресацию [BP+D8], где D8 = 0 (в программе на языке Ассемблера можно записать обращение к операнду, как [BP], но Ассемблер автоматически переведет его в форму [BP+0]). Причину, почему использование [BP] невозможно, мы узнаем позже, когда изучим кодирование команд.
Итак, смысл приведенной выше формулы ясен: процессор складывает содержимое базового регистра, индексного регистра и смещение (displacement). Полученная величина является адресом, а точнее, смещением (offset).
В каком же сегменте вычисляется это смещение? Здесь действует правило: если в выражении встречается BP, то полный адрес вычисляется с использованием содержимого сегмента стека SS, во всех остальных случаях используется сегмент данных DS.
Отдельные комбинации регистров, входящих в EA, имеют свои названия. В литературе по поводу названий методов адресации нет единого мнения. Чаще употребляются следующие наименования.
1)[BX], [SI], [DI] (не [BP]!) — регистровая косвенная адресация.
2)[BX+20], 20[BX], [BP+8] — базовая адресация.
3)[SI–4], –4[SI], [DI+6] — индексная адресация.
4)[BX+SI+2], [BX][SI+2], 2[BX][SI] — базовая индексная адресация со смещением. Все три выражения эквивалентны. Предпочтительнее ис-
пользовать первое из них.
Остается неясным, чем базовый метод адресации отличается от индексного. В 16-разрядном режиме — ничем! Эти названия, по-видимому, были даны "на вырост", с учетом перспективы. Когда мы приступим к изучению 32-разрядного режима, то увидим, чем отличаются эти два режима.
9.4. Пример программы обработки массива (задание A4)
9.4.1. Формулировка задания
Дан массив A из 16 байтов. Скопировать его в массив B, заменяя элементы, равные 2, на нуль. Поместить в массив C адреса (смещения) измененных элементов из массива A. Сосчитать количество элементов, которые не подверглись изменению.
Отчет должен содержать: текст программы с комментариями, входные и выходные значения массивов.
9.4.2. Программа
Разместим массив A, начиная с адреса 200, массив B — с адреса 210 (адреса 16-ричные!), массив C — с адреса 220.
Приведем текст программы.
mov si,200 ; Адрес массива A — в SI
4
mov di,210 ; |
Адрес массива B — в DI |
||
mov bx,220 ; |
Адрес массива C — в BX |
||
xor |
dx,dx |
; |
Счетчик неизменяемых элементов — в DX |
mov |
cx,10 |
; |
Количество элементов (10h = 16) |
;в обрабатываемом массиве — в CX
n:mov al,[si]; Поместим очередной элемент массива A в AL
cmp al,2 |
; |
Сравним его с 2 |
|
jne |
m |
; |
Если элемент равен 2, |
mov |
byte ptr |
[di],0 ; то записать в массив B нуль, |
mov [bx],si ; поместить в C смещение измененного элемента,
inc bx |
; |
и переместить в массиве C указатель |
|
inc |
bx |
; |
на следующий элемент, |
jmp |
c |
|
|
m:mov [di],al ; иначе — записать в B элемент из A,
inc dx |
; увеличить счетчик неизменяемых элементов, |
|
c: inc si |
; переместить указатели в массиве A |
|
inc di |
; |
и в массиве B |
loop n |
; |
Конец цикла |
nop |
|
|
Тестовые данные выберем следующие: до выполнения программы:
массив A: 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
массив B: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
массив C: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
после выполнения программы:
массив A: 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 (без изменений) массив B: 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
массив C: 200, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 регистр DX = 14 = 0Eh.
Текст программы и данные составляют содержание письменного отчета.
9.4.3. Выполнение задания в отладчике Turbo Debugger.
1) Запуск Turbo Debugger. Ввод программы.
C:\prog\>td.exe
На экране появится окно CPU. Сейчас окно CPU занимает часть экрана. Нажмем F5 (Zoom), и окно будет "распахнуто" до размеров экрана. Перейдем к выполнению задания. Находясь в панели кода, начнем ввод ко-
манд программы. Нажмем букву m (первая буква команды mov si,200). Появляется окно "Enter instruction to assemble" (введите команду, которую нужно ассемблировать). Завершаем ввод команды и нажимаем Enter. Встроенный ми- ни-ассемблер немедленно преобразует ее в машинный код. Для ввода следующей команды вызываем команду ассемблирования из локального меню нажатием Ctrl+A (или Alt+F10→Assemble...). В появляющемся диалоговом окне мы видим ранее введенную команду mov si,0200. Нажимем клавишу ↓ (ранее введенная команда становится выделенной). Нажимаем клавишу End (выделение снимается). Редактируем команду, чтобы получить mov di,0210. Аналогично набираем остальные команды.

5
cs:0100►BE0002 |
mov |
si,0200 |
cs:0103 BF1002 |
mov |
di,0210 |
cs:0106 BB2002 |
mov |
bx,0220 |
cs:0109 33D2 |
xor |
dx,dx |
cs:010B B91000 |
mov |
cx,0010 |
cs:010E 8A04 |
mov |
al,[si] |
cs:0110 3C02 |
cmp |
al,02 |
cs:0112 7509 |
jne |
011D |
cs:0114 C60500 |
mov |
byte ptr [di],00 |
cs:0117 8937 |
mov |
[bx],si |
cs:0119 43 |
inc |
bx |
cs:011A 43 |
inc |
bx |
cs:011B EB03 |
jmp |
0120 |
cs:011D 8805 |
mov |
[di],al |
cs:011F 42 |
inc |
dx |
cs:0120 46 |
inc |
si |
cs:0121 47 |
inc |
di |
cs:0122 E2EA |
loop |
010E |
cs:0124 90 |
nop |
|
Команды перехода (адреса 010D и 011B) приходится набирать дважды: сначала как jne 100 и jmp 100 (так как нам неизвестно числовое значение меток m и c), и только после ввода всего текста, когда мы выясним, что m = 011D, а с = 0120, мы можем исправить эти команды, поставив там нужные числовые значения меток, т.е. адреса перехода.
2) Сохраним код программы. Для этого, нажимая Shift+Tab, перейдем в панель данных. Вызовем локальное меню: Alt+F10. Выбираем команду Block. Появляется новое меню (на это указывает стрелка, замыкающая название команды Block). Меню показано на рис. 9.2.
Clear...
Move...
Set...
Read...
Write...
Рис. 9.2.
Выбираем команду Write.... Появляется окно с запросом имени файла: "Enter write file name". В поле ввода File Name введем имя файла a4v0.com. Имя,
разумеется, может быть любым, а вот расширение — только .com, не .exe. Нажимаем Enter.
Появляется новое окно с запросом адреса блока памяти и количества байтов в блоке: Enter memory address, count. Наша программа расположена, начиная с адреса CS:0100. В панели данных подразумевается, что сегментная часть адреса блока находится в регистре DS. Но в панели регистров мы видим, что содержимое сегментных регистров CS и DS совпадает. Поэтому адрес блока можно задать как 100 — только смещение (offset), без указания сегментной части адреса. Количество записываемых байтов: 0124h – 00FFh = 25h. Итак, в поле ввода указываем через запятую два числа: 100, 25, и нажимаем Enter.
6
Заметим, что если бы размер программы составил бы 2Dh, то ввод 100, 2d был бы ошибкой: в файл было бы записано всего два байта, т.к. d — суффикс десятичного числа. В этом случае обязательно надо указывать суффикс шестнадцатеричного числа: 100, 2dh.
3) Введем исходные данные. Сейчас активна панель данных.
Перейдем на адрес 200: Alt+F10→Goto. Появится окно с запросом адреса, на который надо перейти: Enter address to position to. Набираем 200 и нажимаем Enter. В панели данных отображается содержимое байтов, начиная с адреса
DS:0200.
Сначала обнулим массивы A,B,C: Alt+F10→Block→Clear. Появляется уже знакомый запрос: Enter memory address, count. Вводим 200,40 и нажимаем Enter.
Теперь введем единицы в массив A: Alt+F10→Block→Set. В ответ на запрос
Enter address, count, byte value вводим 200,10,1 и нажимаем Enter. Убеждаемся,
что 16 байтов, начиная с адреса 200, содержат единицы.
Подправим первые три элемента массива A: Alt+F10→Change. В ответ на запрос Enter new data bytes вводим 2,1,2 и нажимаем Enter.
Исходные данные подготовлены. Может быть, имеет смысл сохранить файл a4v0.com еще раз, разместив в нем исходные данные. Для этого надо повторить действия предыдущего пункта, только размер программы будет иной: 240h – 0FFh = 141h. При этом в файле сохранится и "мусор" из диапазона адресов 125h
–1FFh.
4) Разместим на экране окна для отображения содержимого массивов A и B. Массив C будем отображать в панели данных окна CPU.
Массив A. F10→View→Dump. На экране появляется окно с содержимым, аналогичным панели данных. Alt+F10→Goto. Вводим 200.
Переместим окно в левый нижний угол: Ctrl+F5 (окно приобретает тонкую зеленую рамку) и клавишами перемещения курсора перемещаем его. Затем нажимаем Shift и клавиши перемещения курсора — окно меняет размеры. Добиваемся, чтобы в окне отображалось ровно 16 байтов — это две строки. Нажатием Enter положение и размеры окна закрепляются, зеленая рамка заменяется обычной.
Операцию изменения положения и размеров окна проще осуществлять мышью. Для изменения положения окна ухватите его мышью за верхнюю рамку и перемещайте. Для изменения размера окна ухватите его мышью за правый нижний угол рамки.
Массив B. F10→View→Another→Dump. Появляется окно. Переходим к адресу ds:210: Alt+F10→Goto, вводим 210. Переместим окно в правый нижний угол и изменим его размеры, так чтобы отображалось 16 байтов.
Массив C. Перейдем в окно CPU. Это можно сделать двумя способами: либо последовательно нажимая F6 (переход из окна в окно), либо нажимая Alt+номер_окна, в нашем случае Alt+1. В панели данных перейдем на адрес 220. Массив C — это массив слов. Отобразим содержимое панели как слова: Alt+F10→Display as→Word. Изменим размер окна CPU так, чтобы на экране были размещены все три окна по следующей схеме (рис. 9.3):
код |
регистры |
флаги |
|
|
|
данные — массив C |
стек |
|
|
|
|
данные — массив A |
данные — массив B |
|
|
|
|
7
Рис. 9.3.
5) Программу на языке Ассемблера никогда не следует сразу запускать на выполнение. Ее нужно обязательно прогонять по шагам, так как ошибки в таких программах сделать легче, чем в программах на языке высокого уровня, а последствия этих ошибок — куда тяжелее.
Мы находимся в окне CPU, в панели данных. Чтобы перейти в панель кода, нажмем Tab. В счетчике команд IP находится адрес 100, что соответствует указателю на первую команду нашей программы в панели кода. При последовательном нажатии на F7 указатель будет перемещаться по программе. Внимательно прослеживаем содержимое регистров и ячеек памяти. Если должна выполняться команда работы с памятью, то справа вверху в панели кода отображается текущее содержимое ячейки памяти. Это полезно иметь в виду, чтобы быть уверенным, что из памяти действительно выбирается нужная информация (т.е. корректно перемещаются указатели, правильно используются методы адресации и т.д.).
Если в программе замечена ошибка, исправляем ее. Для этого возможно придется вставить новые команды или удалить существующие. Чтобы заново не набирать команды, расположенные вслед за исправленными командами (если они занимают больше места в памяти), воспользуйтесь в панели данных командой перемещения данных (в том числе и кода!) в памяти: Alt+F10→Block→Move. Разберитесь самостоятельно, как работать с этой командой.
Если нужно вновь прогнать программу по шагам, сначала следует в счетчик команд занести 0100 — адрес первой команды. Для этого переместитесь в панели кода на команду с адресом 100: Alt+F10→Goto и введите 100. Будет выделена команда с адресом 100. А теперь сделаем так, чтобы IP содержал адрес этой команды: Alt+F10→New cs:ip. В строке подсказки при выборе этого пункта ме-
ню вы увидите: Set the cs:ip to the current location — установить CS:IP в текущее положение курсора. Эквивалентная команда — Ctrl+N. Теперь вновь можно нажимать F7.
Выполнить программу как единое целое можно так: выделим команду nop в конце программы. При этом в IP, разумеется, должен быть стартовый адрес программы. Нажмем клавишу F4 (Here — здесь). Программа будет выполнена.
9.5. Еще примеры.
Для усвоения приемов работы с отладчиком разберите еще два примера. Обязательно выполните их на компьютере.
Пример. В массивах A и B (каждый из 8 слов) расположены «сверхдлинные» целые числа. Выполнить над ними сложение: A += B. Адреса (смещения) тех элементов массива A, при вычислении которых произошел перенос единицы в следующее слово, записать в массив C. Вычислить их количество.
Для эффективного решения этой задачи познакомимся с командами изменения флага CF.
Перечислим команды для изменения флага CF.
Сбросить флаг переноса |
clc |
CF 0 |
CLear CF |
|
CF = 0 |
|
|
8 |
|
|
|
|
|
Инвертировать флаг переноса |
cmc |
CF CF |
|
CoMplement CF |
|
CF = 0 |
|
|
|
|
|
Установить флаг переноса |
stc |
CF 1 |
|
SeT CF |
|
CF = 1 |
|
Расположим массив A по адресу 200h, B по адресу 210h = 200h + 10h, C по адресу 220h = 210 +10h
Тест
До
AFFFF, 2, FFFE, FFFD, FFFС, FFFС, FFFС, FFFС
B1, 3, 2, 2, 2, 2, 2, 2 (число: 00020002000200020002000200030001)
C0, 0, 0, 0, 0, 0, 0, 0
После
A0, 6, 0, 0, FFFF, FFFE, FFFE, FFFE
B1, 3, 2, 2, 2, 2, 2, 2
C200, 204, 206, 0, 0, 0, 0, 0
mov si,200 ; Адрес массива A — в SI mov di,210 ; Адрес массива B — в DI mov bx,220 ; Адрес массива C — в BX xor dx,dx ; Счетчик элементов — в DX
mov cx,8 ; Количество элементов в массиве A — в CX clc ; Обнулить флаг CF,
;чтобы отдельно не обрабатывать младший элемент
n:mov ax,[si] ; Выполнить сложение очередных элементов adc ax,[di]
mov [si],ax
jnc m ; Если возник единичный бит переноса, inc dx ; то увеличить счетчик
mov [bx],si ; и записать адрес в массив C
inc bx ; Переместить указатель в массиве C inc bx
m:inc si ; Переместить указатель в массиве A inc si
inc di ; Переместить указатель в массиве B inc di
loop n nop
Код в отладчике |
|
|
cs:0100 BE0002 |
mov |
si,0200 |
cs:0103 BF1002 |
mov |
di,0210 |
cs:0106 BB2002 |
mov |
bx,0220 |
cs:0109 33D2 |
xor |
dx,dx |
9
cs:010B B90800 |
mov |
cx,0008 |
cs:010E F8 |
clc |
|
cs:010F 8B04 |
mov |
ax,[si] |
cs:0111 1305 |
adc |
ax,[di] |
cs:0113 8904 |
mov |
[si],ax |
cs:0115 7305 |
jnb |
011C |
cs:0117 42 |
inc |
dx |
cs:0118 8937 |
mov |
[bx],si |
cs:011A 43 |
inc |
bx |
cs:011B 43 |
inc |
bx |
cs:011C 46 |
inc |
si |
cs:011D 46 |
inc |
si |
cs:011E 47 |
inc |
di |
cs:011F 47 |
inc |
di |
cs:0120 E2ED |
loop |
010F |
cs:0122 90 |
nop |
|
Дамп памяти после выполнения программы ds:0200 0000 0006 0000 0000 ds:0208 FFFF FFFE FFFE FFFE ds:0210 0001 0003 0002 0002 ds:0218 0002 0002 0002 0002 ds:0220 0200 0204 0206 0000
Пример. Дан массив A из 16 байтов. Из байтов, равноотстоящих от середины массива, образовать слово. При этом байт с меньшим адресом становится младшим. Начинать с крайних элементов, продвигаясь к середине. Если слово не делится на 3 (числа знаковые), то записать его в массив слов B, а адрес (смещение) младшего байта в массив С. Сосчитать количество таких элементов.
Расположим массив A по адресу 200h, B по адресу 210h = 200h + 10h, C по адресу 220h = 210 +10h
Тест Сначала составим пары чисел.
0 и 4 → 04, 04 mod 3 = 1 → не делится; ff и fd → fffd = –3, –3 mod 3 = 0 → делится;
Вкалькуляторе TD вычисляем, что 2567h mod 3 дает результат 2 → не делится.
Вкалькуляторе TD набираем десятичное число 1452d (его сумма цифр делится на 3). А его 16-ричный эквивалент: 5AC;
Для оставшихся элементов выбираем 0 и 1 → 01 → не делится.
Итак, данные для тестирования До
A4, 0FDh, 67h, 0Ach, 1, 1, 1, 1, 0, 0, 0, 0, 5, 25, 0FFh, 0
B0, 0, 0, 0, 0, 0, 0, 0
C0, 0, 0, 0, 0, 0, 0, 0
После
A 4, 0FDh, 67h, 0Ach, 1, 1, 1, 1, 0, 0, 0, 0, 5, 25, 0FFh, 0
10
B4, 2567, 1, 1, 1, 1
C200, 202, 204, 205, 206, 207, 0, 0
количество: 6
Идея алгоритма: настроим указатели на первый и последний элемент массива, и будем продвигаться к середине.
mov si,200 ; Адрес первого элемента массива A - в SI mov di,20F ; Адрес первого элемента массива A - в DI mov bx,210 ; Адрес массива B в BX
xor bp,bp ; Счетчик подходящих элементов – в BP mov cx,8 ; Количество пар элементов – в CX
n:mov al,[si]; Младший элемент — в AL mov ah,[di]; Старший элемент — в AH cwd ; Знаковое делимое в DX:AX push bp
push ax
mov bp,3 ; Делитель в BP idiv bp
or dx,dx ; Если число не делится на 3, je m
pop ax ; то восстановить AX pop bp ; и счетчик элементов
mov [bx],ax; записать AX в массив B,
mov [bx+10],si ; а адрес младшего элемента в массив C inc bp ; Увеличить счетчик
inc bx ; Переместить указатель в B
inc bx
m:inc si ; Переместить указатели в массиве A dec di
loop n nop
Здесь может возникнуть вопрос: почему для проверки делимости числа, размещенного в AX, мы не воспользовались более экономным фрагментом
mov dh,3 idiv dh or ah,ah je m
Напомним ответ: здесь возможно переполнение при делении (более подробно это обсуждалось в гл. 8).
В этой программе нам не хватило регистров для размещения промежуточных результатов, поэтому пришлось воспользоваться стеком для их временного хранения. Вы заметили недостаток программы? Мы увеличиваем стек при обработке каждого элемента, а уменьшаем его только при обработке элемента, который не делится на 3. Исправить программу можно, например, так: после команды pop bp вставить команду sub sp,4, а после метки m команду add sp,4. После первой из этих команд указатель стека будет принудительно перемещен вниз на два слова, а после второй — вернется на прежнее место.