u.release, u.version, u.machine);
return 0;
}
Глава 9
Встроенный ассемблерный код
Сегодня лишь немногие программисты используют в своей практике язык ассемблера. Языки высокого уровня, такие как С и C++, поддерживаются практически на всех архитектурах и обеспечивают достаточно высокую производительность программ. Для тех редких случаев, когда требуется встроить в программу ассемблерные инструкции, в коллекции GNU-компиляторов (GCC) предусмотрены специальные средства, учитывающие особенности конкретной архитектуры.
Встроенными ассемблерными инструкциями следует пользоваться осторожно, так как они являются системно-зависимыми. Например, программу с инструкциями архитектуры x86 не удастся скомпилировать на компьютерах PowerPC. В то же время такие инструкции позволяют напрямую обращаться к аппаратным устройствам, вследствие чего программный код выполняется чуть быстрее.
В программы, написанные на языках С и C++, ассемблерные инструкции встраиваются с помощью функции asm()
. Например, на платформе x86 команда
asm("fsin" : "=t" (answer) : "0" (angle));
является эквивалентом следующей инструкции языка C:[30]
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));