В программы, написанные на языках С и C++, ассемблерные инструкции встраиваются с помощью функции asm()
. Например, на платформе x86 команда
asm("fsin" : "=t" (answer) : "0" (angle));
является эквивалентом следующей инструкции языка C: [30] Выражение sin(angle) обычно преобразуется в вызов функции библиотеки libm , но если задать флаг -O1 (или включить более сильную оптимизацию), компилятор gcc заменит вызов функции простой ассемблерной инструкцией fsin .
answer = sin(angle);
Обратите внимание на то, что, в отличие от обычных ассемблерных инструкций, функция asm()
позволяет указывать входные и выходные операнды, используя синтаксис языка С.
Подробнее узнать об инструкциях архитектуры x86, используемых в настоящей главе, можно по следующим адресам: http://developer.intel.com/design/pentiumii/manuals
и http://www.x86-64.org/documentation
.
9.1. Когда необходим ассемблерный код
Инструкции, указываемые в функции asm()
, позволяют программам напрямую обращаться к аппаратным устройствам, поэтому полученные программы выполняются быстрее. Ассемблерные инструкции используются при написании кода операционных систем. Например, файл /usr/include/asm/io.h
содержит объявления команд, осуществляющих прямой доступ к портам ввода-вывода. Можно также назвать один из исходных файлов ОС Linux — /usr/src/linux/arch/i386/kernel/process.s
; в нем с помощью инструкции hlt
реализуется пустой цикл ожидания.
Прибегать к ассемблерным инструкциям как к средству ускорения работы программы следует лишь в крайнем случае. Современные компиляторы достаточно сложны и прекрасно осведомлены об особенностях работы процессоров, для которых они генерируют код. Часто они создают цепочки кодов, которые кажутся неэффективными или неоптимальными, но на самом деле такие последовательности инструкций выполняются быстрее других. В подавляющем большинстве случаев можно положиться на оптимизирующие способности компиляторов.
Иногда одна или две ассемблерные команды способны заменить целую группу высокоуровневых инструкций. Например, чтобы определить позицию самого старшего значащего бита целого числа в языке С, требуется написать цикл, тогда как во многих ассемблерных языках для этой цели существует операция bsrl
. Ее использование будет продемонстрировано в разделе 9.4, "Пример".
9.2. Простая ассемблерная вставка
Вот как с помощью функции asm()
осуществляется сдвиг числа на 8 битов вправо:
asm("shrl $8, %0" : "=r" (answer) : "r" (operand) : "cc");
Выражение в скобках состоит из секций, разделенных двоеточиями. В первой секции указана ассемблерная инструкция и ее операнды. Команда shrl
осуществляет сдвиг первого операнда на указанное число битов вправо. Первый операнд представлен выражением %0
. Второй операнд — это константа $8
.
Во второй секции задаются выходные операнды. Единственный такой операнд будет помещен в C-переменную answer
, которая должна быть адресуемым (левосторонним) значением. В выражении "=r"
знак равенства обозначает выходной операнд, а буква r
указывает на то, что значение переменной answer
заносится в регистр.
В третьей секции перечислены входные операнды. Переменная operand
содержит значение, подвергаемое битовому сдвигу. Выражение "r"
означает, что значение переменной записывается в регистр.
Выражение "cc"
в четвертой секции говорит о том. что инструкция меняет значение регистра cc
(содержит код завершения).
9.2.1. Преобразование функции asm() в ассемблерные инструкции
Компилятор gcc
интерпретирует функцию asm()
очень просто: он генерирует ассемблерные инструкции, обрабатывающие указанные входные и выходные операнды, после чего заменяет вызов функции заданной инструкцией. Никакой дополнительный анализ не выполняется.
Например, следующий фрагмент программы:
double foo, bar;
asm("mycool_asm %1, %0" : "=r" (bar) : "r" (foo));
будет преобразован в такую последовательность команд x86:
movl -8(%ebp),%edx
movl -4(%ebp),%ecx
#APP
mycool_asm %edx, %edx
#NO_APP
movl %edx,-16(%ebp)
movl %ecx,-12(%ebp)
Переменные foo
и bar
занимают по два слова в стеке в 32-разрядной архитектуре x86. Регистр ebp
ссылается на данные, находящиеся в стеке.
Первые две команды копируют переменную foo в регистры edx
и ecx
, с которыми работает инструкция mycool_asm
. Компилятор решил поместить результат в те же самые регистры. Последние две команды копируют результат в переменную bar
. Выбор нужных регистров и копирование операндов осуществляются автоматически.
Читать дальше