В сборке x86, когда я должен использовать глобальные переменные вместо локальных переменных? - PullRequest
2 голосов
/ 26 марта 2019

Я создаю несколько небольших программ со сборкой x86, и я впервые использую язык низкого уровня, поэтому я к этому не привык.

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

Под глобальными переменными я подразумеваю данные, созданные в сегментах .bss и .data, а под локальными переменными - данные, размещенные в стеке текущей процедуры с использованием указателя стека.

Сейчас я использую локальные переменные и параметры намного больше, чем глобальные переменные.

Заранее спасибо.

1 Ответ

4 голосов
/ 26 марта 2019

Да, предпочитайте локальные объекты, которые вы храните в регистрах или в стеке, если необходимо.

«Переменная» - это концепция высокого уровня, которая в действительности не существует в 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
...