Да, предпочитайте локальные объекты, которые вы храните в регистрах или в стеке, если необходимо.
«Переменная» - это концепция высокого уровня, которая в действительности не существует в asm.Так что это просто вопрос того, где вы храните данные, над которыми работаете.Но, конечно, локальный и глобальный var - хороший способ поговорить о статическом хранилище (.data / .bss / .rodata) и о стековой памяти, если вы думаете о неоптимизированный Cгде каждая переменная действительно имеет адрес в памяти.
В asm код с меньшим количеством инструкций обычно легче понять.Удаление инструкций сохранения / перезагрузки mov
в пользу простого хранения данных в регистрах обычно облегчает выполнение.Радость написания в asm состоит в том, чтобы находить способы выполнять ту же работу с меньшим количеством (и / или более дешевых) инструкций, а бесполезное хранение / перезагрузка в память - противоположность этому.Это делает ваш код уродливым, IMO.
Глобалы засасывают asm по всем причинам, по которым они засасывают языки более высокого уровня (неясный поток данных при чтении / записи функций), а также другие соображения, которые могут не сработать.Подумайте на языке высокого уровня: каждая инструкция, которая использует статический адрес, такой как [my_var]
, имеет 4-байтовый disp32
как часть режима адресации, в отличие от [esp+8]
, требующего только 2 дополнительных байта (SIB из-за ESPв качестве основы, и disp8, потому что +8
помещается в расширенное знаком 8-разрядное целое число).Или, если вы создаете кадр стека с помощью EBP, вы сохраняете байт SIB в режимах адресации.
Глобальные значения могут быть оправданы в игрушечных программах, которые в основном представляют собой только одну функцию, если вы не заботитесь об эффективности и предпочитаетеопределите вашу схему памяти с метками и dd
/ dw
/ db
вместо просто смещений в кадре стека.Но часто вы можете просто хранить все в регистрах в этом случае.(Особенно на x86-64, где у вас есть 15 регистров GP, отличных от указателя стека, по сравнению с IA-32, имеющим только 7 или 6, если вы назначаете EBP указателем кадра.)
Использование большого количества глобальных переменных в примерах / руководствах asm, возможно, является привычкой в старом стиле от старых ISA, таких как 6502 или 8051, которые не имели режимов адресации относительно указателя стека , и, следовательно, локальные переменные в стеке вызовов были плохимивещь.(См. Почему компиляторы с C на Z80 производят плохой код? )
Возможно, это также / вместо этого делается как простой способ именования переменных с целью создания примера, но в asm этодля чего комментарии.Нет компилятора, который бы превратил ваш самодокументированный код в эффективный код.Или вы можете сделать то, что делает вывод MSVC asm, и определить константы времени сборки для смещения каждого локального элемента относительно стекового фрейма.например,
foo equ -12
func:
push ebp
mov ebp, esp
sub esp, 24
...
mov eax, [ebp + foo]
leave
ret
Еще лучше: храните свои локальные переменные в регистрах
Для большинства переменных обычно нет необходимости помещать их в память где-либо.Используйте комментарии, чтобы отслеживать, где находится переменная или выражение.
Если это не влияет на эффективность, часто вы можете иметь соответствие 1: 1 между регистрами и переменными высокого уровня, о которых вы думаете, когдаразработка алгоритма.Например, возможно x
остается в edi
для всей функции, включая все блоки после ветвления.(И некоторые другие регистры в основном используются как пустое место для вычислений и содержимого, загружаемого из памяти.)
В этом случае у вас будет блок комментариев в верхней части функции, документирующей это.Если некоторые из регистров установлены в верхней части функции, эти строки источника могут быть хорошими местами для таких комментариев.
Память-адресат sub dword [loop_counter], 1
имеет 6 циклов задержки на типичных современных ISA x86 (5 циклов пересылки магазина + 1 цикл ALU).Если вы используете это как часть цикла, он будет работать с best одна итерация на 6 циклов.Это часть того, почему компиляторы C с отключенной оптимизацией делают такой медленный код.Делать это вручную - это в основном стрелять себе в ногу.
dec ecx
/ jnz
имеет только задержку в 1 цикл, поэтому цикл без сохранения / перезагрузки как часть зависимости, переносимой циклом, может выполняться с частотой 1 итерация за такт.(Для циклов до 4 мопов на текущих процессорах Intel; до 5 инструкций, если dec / jnz или cmp/jcc
в нижнем макросе сливается в один моп. В противном случае вы сталкиваетесь с узкими местами на входе. Говоря об этом, памятьОперации чтения-изменения-записи -destination всегда по крайней мере 2 моп.)
Когда использовать глобальные переменные
Выделение большого массива в BSS легко для тестирования.Затем вы можете получить адрес в регистр с mov edi, array
в синтаксисе NASM или mov edi, OFFSET array
в синтаксисе MASM.Таким образом, вы можете использовать это для тестирования кода, который написан, чтобы взять точку для массива в качестве входных данных.
Статические константные данные полезны
Наиболее распространенный вариант использования - это, вероятно, строки в section .rodata
(или section .rdata
в Windows).
section .rodata ; linked as part of the TEXT segment
msg: db "Hello World", 10
msglen equ $ - msg ; assemble-time constant
Часто вам требуется строка в памяти для передачи по ссылке на системный вызов, например write
, или на функцию, подобную puts
или printf
(например, как строка формата).Хранить его в постоянной памяти и материализовать указатель намного проще, чем сохранять строку в памяти из непосредственных элементов, например, с помощью push `rld\n`
или
mov dword [esp], "Hell"
mov dword [esp+4], "o Wo"
...
mov ecx, esp ; pointer to the string