Инструкция, ответственная за ошибку, такова:
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
- это символ, поэтому 8 бит. Вы пытаетесь переместить его в 32-битный регистр. Вы должны использовать 8-битный регистр или расширение знака/нуля в зависимости от ситуации. Конечно, ваш код плохая практика, если вы хотите что-то в определенном регистре, попросите компилятор поместить это туда, не используйтеmov
. - person Jester   schedule 13.07.2017