Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / Gl19

.doc
Скачиваний:
16
Добавлен:
20.05.2014
Размер:
37.38 Кб
Скачать

Вставка ассемблерного кода в программу на языке Си

Компилятор 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

Соседние файлы в папке Лекции