
Лекции / Gl19
.docВставка ассемблерного кода в программу на языке Си
Компилятор Visual C++ позволяет делать вставки на языке Ассемблера в программу на языках Си и C++. Для этого в текст программы включают директиву
__ asm {
<инструкции на языке ассемблера>
}
Обратите внимание, что ключевое слово __asm начинается с двух символов подчеркивания.
К сожалению, в ассемблерном коде нельзя использовать макросредства (некоторые счастливые исключения будут рассмотрены ниже).
Пример. Напишем программу, которая выполняет вычисления по формуле
res = num * 2^pow,
где num и pow — целые.
файл p2.c
#include <stdio.h>
int main()
{
int num, pow, res;
puts("num pow");
scanf("%d %d", &num, &pow);
res = num;
__asm {
mov ecx,pow
shl res,cl
}
printf("%d * 2^%d = %d\n", num, pow, res);
return 0;
}
Разумеется, то же самое легко реализовать средствами чистого Си:
res = num << pow;
Сеанс работы с программой:
num pow
3 5
3 * 2^5 = 96
Press any key to continue
Итак, какие можно сделать выводы из представленной программы. Ассемблерный код начинается с ключевого слова __asm (два подчеркивания!). Это ключевое слово открывает блок кода, заключенный в фигурные скобки.
Упражнение. Реализуйте два экземляра приведенной программы: первый — без ассемблерной вставки, а с оператором res = num << pow; второй — с ассемблерной вставкой. При этом в обоих вариантах для чистоты эксперимента сделайте вариант Release и оптимизацию по скорости выполнения. Какой вывод можно сделать, сравнив машинный код вариантов?
Ответ: во втором варианте размер кода резко увеличивается. Связано это с тем, что в этом варианте компилятор не может размещать переменные в регистрах, а вынужден делать их автоматическими. Получается, что ассемблерная вставка может привести к ухудшению, а не улучшению кода в целом.
Во встроенном ассемблере имеются три оператора времени компиляции LENGTH, SIZE, и TYPE:
-
LENGTH возвращает количество элементов в массиве (для простой переменной это значение равно 1);
-
TYPE возвращает размер переменной в байтах (если оператор применен к имени массива, то возвращается размер элемента массива);
-
SIZE возвращает размер переменной или массива, это число является произведением значений, возвращаемых LENGTH и TYPE.
Пример. Пусть в программе описан массив int arr[8]; Построим таблицу для значений, возвращаемых LENGTH, SIZE, и TYPE и соответствующих операторов Си.
LENGTH arr sizeof(arr)/sizeof(arr[0]) 8
TYPE arr sizeof(arr[0]) 4
SIZE arr sizeof(arr) 32
Упражнение. Выведите значения операторов LENGTH, SIZE, и TYPE применительно к массиву структур
struct str {
int age;
char name[8];
} s[3];
и объясните полученный результат. Замените в описании структуры char name[8] на char name[9]. Объясните полученный результат (для этого заполните элементы массива структур данными и изучите содержимое окна Memory при выполнении программы).
Пример. Сосчитать количество единичных битов в целой переменной x.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int x, ones, i;
char num[33];
int main()
{
x = 0x8025;
__asm {
mov eax,x
xor ebx,ebx
mov cx, 8 * TYPE x
n: rol eax,1
adc ebx,0
loop n
mov ones, ebx
}
memset(num, 0, sizeof(num));
_itoa( x, num, 2);
printf("x = %s, the amount of 1's is %d\n", num, ones);
return 0;
}
Программа выводит:
x = 1000000000100101, the amount of 1's is 4
В этой программе переменные сделаны глобальными, чтобы легче было анализировать машинные коды. В ассемблерных вставках нельзя определять данные. Это можно делать только в самой программе на Си. Зато внутри блока __asm можно обращаться к переменным по их именам.
Большим преимуществом таких смешанных программ является то, что ввод-вывод можно делать средствами языка Си и его стандартных библиотечных функций. В нашем примере переменная x выводится в двоичном формате. Для этого выделен массив символов num. Его размерность 33: на двоичное представление числа отведено 32 байта и еще один байт на терминатор строки. С помощью библиотечной функции memset массив num обнуляется. Двоичная строка формируется с помощью функции _itoa (не входящей в стандартную библиотеку; в Turbo C такая функция называется itoa — без предваряющего подчеркивания). Первый аргумент — преобразуемое число, второй — строка для результата преобразования, третий — основание системы счисления.
При компиляции программы вступает в работу встроенный ассемблер.
Посмотрим, как он реагирует на ошибки. Заменим команду mov eax,x командой mov ax,x. Получим сообщение об ошибке:
c(12) : error C2443: operand size conflict
Но сообщение это относится к строке, в которой размещена команда xor ebx,ebx, т.е. следующей строке программы. Это часто сбивает с толку.
Еще одно замечание. Если вы используете строковые команды и установили флаг направления, то перед выходом из ассемблерного блока надо обязательно сбросить этот флаг. В противном случае некоторые библиотечные программы могут выполняться неверно. Проиллюстрируем это примером. Вставим в ассемблерный блок команду std.
mov ones, ebx
std
}
Тогда программа выведет:
x = 0, the amount of 1's is 0
Имеется возможность вставлять машинные коды инструкций с помощью псевдоинструкции _emit.
Приложение. Реализация двух вариантов кода (ответ к упражнению).
00401000 sub esp,8
00401003 push 407048h
00401008 call 00401088
0040100D lea eax,[esp+8]
00401011 lea ecx,[esp+4]
00401015 push eax
00401016 push ecx
00401017 push 407040h
0040101C call 00401071
00401021 mov eax,dword ptr [esp+10h]
00401025 mov ecx,dword ptr [esp+14h]
00401029 mov edx,eax
0040102B shl edx,cl
0040102D push edx
0040102E push ecx
0040102F push eax
00401030 push 407030h
00401035 call 00401040
0040103A xor eax,eax
0040103C add esp,28h
0040103F ret
00401000 push ebp
00401001 mov ebp,esp
00401003 sub esp,0Ch
00401006 push 407048h
0040100B call 00401098
00401010 lea eax,[ebp-0Ch]
00401013 lea ecx,[ebp-4]
00401016 push eax
00401017 push ecx
00401018 push 407040h
0040101D call 00401081
00401022 mov edx,dword ptr [ebp-4]
00401025 mov dword ptr [ebp-8],edx
00401028 mov ecx,dword ptr [ebp-0Ch]
0040102B shl dword ptr [ebp-8],cl
0040102E mov eax,dword ptr [ebp-8]
00401031 mov ecx,dword ptr [ebp-0Ch]
00401034 mov edx,dword ptr [ebp-4]
00401037 push eax
00401038 push ecx
00401039 push edx
0040103A push 407030h
0040103F call 00401050
00401044 add esp,20h
00401047 xor eax,eax
00401049 mov esp,ebp
0040104B pop ebp
0040104C ret