Несоответствие типа встроенной сборки AT&T

Я изучаю ассемблер и не нашел ничего, что помогло бы мне в этом. Это вообще возможно? Я не могу заставить это работать.

Я хочу, чтобы этот код принимал значение «b», помещал его в %eax, а затем перемещал содержимое %eax в мой вывод и печатал этот символ ASCII, в данном случае «0».

char a;
int b=48;
__asm__ ( 
//Here's the "Error: operand type mismatch for `mov'
"movl %0, %%eax;"
"movl %%eax, %1;"

:"=r"(a)
:"r" (b)
:"%eax"
);

printf("%c\n",a);

person Omar Boninsegna    schedule 13.07.2017    source источник
comment
Поскольку встроенный ассемблер по своей сути зависит как от компилятора, так и от платформы, вам необходимо идентифицировать оба, чтобы иметь большие шансы получить достойный ответ.   -  person Jonathan Leffler    schedule 13.07.2017
comment
Интересно, какое это имеет отношение к AT&T...   -  person Eugene Sh.    schedule 13.07.2017
comment
a - это символ, поэтому 8 бит. Вы пытаетесь переместить его в 32-битный регистр. Вы должны использовать 8-битный регистр или расширение знака/нуля в зависимости от ситуации. Конечно, ваш код плохая практика, если вы хотите что-то в определенном регистре, попросите компилятор поместить это туда, не используйте mov.   -  person Jester    schedule 13.07.2017


Ответы (2)


Инструкция, ответственная за ошибку, такова:

movl %0, %%eax

Итак, чтобы выяснить, почему он вызывает ошибку, нам нужно понять, что он говорит. Это 32-битная инструкция MOV (суффикс l в синтаксисе AT&T означает «длинный», он же DWORD). Операндом назначения является 32-битный регистр EAX. Исходным операндом является первый операнд ввода/вывода, a. Другими словами, это:

"=r"(a)

в котором говорится, что char a; должен использоваться как регистр только для вывода.

Таким образом, встроенный ассемблер хочет сгенерировать следующий код:

movl %dl, %eax

(Предполагая, ради аргумента, что a размещается в регистре dl, но его можно было бы так же легко разместить в любом из 8-битных регистров). Проблема в том, что этот код недействителен из-за несоответствия размера операнда. Операнд-источник и операнд-получатель имеют разные размеры: один — 32 бита, а другой — 8 бит. Это не может работать.

Обходной путь - это инструкции movzx/movsx (представленные в 80386), которые перемещают 8-битный (или 16-битный) исходный операнд в 32-битный операнд назначения либо с нулевым расширением, либо со знаком, соответственно. В синтаксисе AT&T форма, которая перемещает 8-битный источник в 32-битный пункт назначения, будет movzbl (для нулевого расширения, используемого со значениями без знака) или movsbl (для знакового расширения, используемого со значениями со знаком).

Но подождите — это неправильный обходной путь. Ваш код недействителен по другой причине: a не инициализирован! И дело не только в том, что a не инициализировано, но вы сказали встроенному ассемблеру через выходные ограничения, что это операнд только для вывода (знак =)! Таким образом, вы не можете читать из него — вы можете только сохранять в него.

У вас есть нотация операнда в обратном порядке. Вы на самом деле хотели примерно следующего:

__asm__( 
        "movl %1, %%eax;"
        "movl %%eax, %0;"

        : "=r"(a)
        : "r" (b)
        : "%eax"
       );

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

    movl $48,  %edx
    movl %edx, %eax
    movl %eax, %dl

что недопустимо, поскольку 32-битный источник (%eax) не может быть перемещен в 8-битное место назначения (%dl). И вы не можете исправить это с помощью movzx/movsx, поскольку они используются для расширения, а не для усечения. Способ написания этого будет следующим:

    movl $48,  %edx
    movl %edx, %eax
    movb %al,  %dl

где последняя инструкция представляет собой 8-битное перемещение из 8-битного исходного регистра в 8-битный регистр назначения.

Во встроенном ассемблере это будет записано так:

__asm__( 
        "movl %1, %%eax;"
        "movb %%al, %0;"

        : "=r"(a)
        : "r" (b)
        : "%eax"
       );

Однако это неправильный способ использования встроенного ассемблера. Вы вручную жестко закодировали регистр EAX внутри блока встроенного ассемблера, что означает, что вам пришлось его затереть. Проблема в том, что это связывает компилятору руки за спиной, когда дело доходит до распределения регистров. Что вы должны сделать, так это поместить все, что входит и выходит из блока встроенного ассемблера, во входные и выходные операнды. Это позволяет компилятору обрабатывать распределение всех регистров наиболее оптимальным образом. Код должен выглядеть следующим образом:

char a;
int  b = 48;
int temp;
__asm__( 
        "movl %2, %0\n\t"
        "movb %b0, %1"

        : "=r"(temp),
          "=r"(a)
        : "r" (b)
        :
       );

Здесь произошло много изменений:

  • Я ввел еще одну временную переменную (соответственно названную temp) и добавил ее в список операндов, предназначенных только для вывода. Это заставляет компилятор автоматически выделять для него регистр, который мы затем используем внутри ассемблерного блока.
  • Теперь, когда мы позволяем компилятору выполнять распределение регистров, нам не нужен список засорения, поэтому он остается пустым.
  • Модификатор b необходим в исходном операнде для инструкции movb, чтобы гарантировать, что используется байтовая часть этого регистра, а не весь 32-битный регистр.
  • Вместо использования точки с запятой в конце каждой ассемблерной инструкции я использовал \n\t (кроме последней). Это то, что рекомендуется для использования во встроенных ассемблерных блоках, и это дает вам более красивые выходные листы сборки, потому что это соответствует тому, что компилятор делает внутри.

Еще лучше было бы ввести символические имена для операндов, чтобы сделать код более читабельным:

char a;
int  b = 48;
int temp;
__asm__( 
        "movl %[input], %[temp]\n\t"
        "movb %b[temp], %[dest]"

        : [temp]  "=r"(temp),
          [dest]  "=r"(a)
        : [input] "r" (b)
        :
       );

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

    movl $48, %eax

а значение 48 уже находится в al, поскольку al — это младшие 8 бит 32-битного регистра eax.

Или вы можете сделать:

    movb $48, %al

что представляет собой просто 8-битное перемещение значения 48 явно в 8-битный регистр al.

Но на самом деле, если вы вызываете printf, аргумент должен передаваться как int (а не char, так как это функция с переменным числом аргументов), поэтому вам определенно нужно:

    movl $48, %eax

Когда вы начинаете использовать встроенный ассемблер, компилятор не может легко его оптимизировать, поэтому вы получаете неэффективный код. Все, что вам действительно было нужно, это:

int a = 48;
printf("%c\n",a);

Что производит следующий ассемблерный код:

    pushl   $48
    pushl   $AddressOfFormatString
    call    printf
    addl    $8, %esp

или, что то же самое:

    movl    $48, %eax
    pushl   %eax
    pushl   $AddressOfFormatString
    call    printf
    addl    $8, %esp

Теперь я представляю, как вы говорите себе что-то вроде: "Да, но если я это сделаю, то я не использую встроенный ассемблер!" На что я отвечаю: точно< /эм>. Здесь вам не нужен встроенный ассемблер, и на самом деле вы не должны не его использовать, потому что он просто вызывает проблемы. Это сложнее писать и приводит к неэффективной генерации кода.

Если вы хотите изучить программирование на ассемблере, возьмите ассемблер и используйте его, а не встроенный ассемблер компилятора C. NASM — популярный и отличный выбор, как и YASM. Если вы хотите продолжать использовать ассемблер Gnu, чтобы вы могли придерживаться этого запутанного синтаксиса AT&T, тогда запустите as.

person Cody Gray    schedule 14.07.2017

Поскольку a определяется как символ (char a;), :"=r"(a) назначит 8-байтовый регистр. 32-байтовый регистр EAX не может быть загружен с 8-байтовым регистром — movl %dl, %eax (movl %0, %%eax) вызовет эту ошибку. Для этой цели существуют инструкции расширения знака и расширения нуля movzx и movsx (синтаксис Intel), в синтаксисе AT&T: movs... и movz....

Сдача

movl %0, %%eax;

to

movzbl %0, %%eax;
person rkhb    schedule 13.07.2017