Как изолировать элементы массива байтов и слов в 64-битном регистре

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

segment .data
  a      dw  4, 234, -212
  b      db  112, -78, 50
  result dq  0
segment .text       
  global main
main:
  mov   rax, [a]        

Я знаю, что решение имеет какое-то отношение к смещениям и индексации, но я не понимаю, как я должен иметь возможность поместить только один элемент массива в регистр.

Что я могу сделать?

1 ответ

Если вы хотите рассматривать свои ценности как подписанные, вы хотите movsx. Предполагая синтаксис NASM:

default rel
; ... declarations and whatever    

    movsx   rax, word [a + 1*2]    ; a is an array of dw = words
    movsx   rcx, byte [b + 1*1]    ; b is an array of db = bytes

    add     rax, rcx
    mov     [result], rax         ; result is a qword

(MASM или GNU .intel_syntax будет использовать word ptr вместо того word, просто добавь ptr спецификатору размера для операнда памяти.)

В 1 может быть регистр вроде [a + rsi*2] или [b + rsi]так что вы можете легко перебрать свои массивы. Ссылка на содержимое ячейки памяти. (режимы адресации x86)

Я написал 1*2вместо 2, чтобы указать, что это индекс 1 (2-й элемент массива), масштабируемый по размеру элемента. Ассемблер оценит константное выражение и просто будет использовать тот же (относительный RIP) режим адресации, что и для[a] но с другим смещением.

Если вам нужно, чтобы он работал в позиционно-независимом коде (где вы не можете использовать [disp32 + register] режим адресации с 32-битным абсолютным адресом символа), lea rdi, [a] (RIP-relative LEA) сначала и сделайте [rsi + rsi*2].


Если вам нужно нулевое расширение, вы должны использовать movzx

    movzx   eax, word [a + 1*2]    ; a is an array of dw = words
    movzx   ecx, byte [b + 1*1]    ; b is an array of db = bytes
    ; word and byte zero-extended into 64-bit registers:
    ; explicitly to 32-bit by MOVZX, and implicitly to 64-bit by writing a 32-bit reg

    ; add     eax, ecx              ; can't overflow 32 bits, still zero-extended to 64
    sub     rax, rcx              ; want the full width 64-bit signed result 
    mov     [result], rax         ; result is a qword

Если бы вы знали, что верхние биты вашего полного результата всегда будут равны нулю, просто используйте EAX (32-битный размер операнда), кроме конца. Преимущества использования 32-битных регистров / инструкций в x86-64

Этот код соответствует C как

static  uint16_t a[] = {...};
static  uint8_t b[] = {...};
static  int64_t result;

void foo(){
    int64_t rax = a[1] - (int64_t)b[1];
    result = rax;    // why not just return this like a normal person instead of storing?
}

Кстати, вы можете посмотреть на вывод компилятора в проводнике компилятора Godbolt и увидеть эти инструкции и режимы адресации.


Обратите внимание, что mov al, [b + 1]загрузит байт и объединит его с младшим байтом RAX.

Обычно вы этого не хотите; movzx- это нормальный способ загрузки байта в современной x86. Современные процессоры x86 декодируют x86 до RISC-подобных внутренних мопов для переименования регистров + выполнения вне очереди. movzxизбегает любой ложной зависимости от старого значения полного регистра. Аналог ARMldrb, MIPS lbu, и так далее.

Слияние с младшим байтом или словом RAX - странная вещь CISC, которую x86 может делать, а RISC - нет.

Вы можете безопасно читать 8-битные и 16-битные регистры (а это необходимо для хранилища слов), но обычно избегайте записи частичных регистров, если у вас нет веской причины и вы понимаете возможные последствия для производительности ( почему GCC не использует частичное регистры?). например, вы обнулили полный пункт назначения перед cmp +setcc al.

Другие вопросы по тегам