Шаг 3.
Оптимизация с помощью ассемблера.
Анатомия функций языка C. Функции, которые возвращают void

    На этом шаге мы рассмотрим функции, которые возвращают void.

    Скомпилируйте с опцией -S программу, содержащую простую функцию языка C, которая возвращает void. Вы должны будете ввести с клавиатуры прописную S. Найдите листинг 1 и из приглашения DOS задайте команду bcc -S voidf. Затем загрузите результирующий файл VOIDF.ASM в текстовый редактор.

    Не считая нескольких вспомогательных строк в начале сгенерированного ассемблерного файла (в этих строках объявляются макросы, компонуются сегменты программ, а также присутствует отладочная информация), в листинге 2 демонстрируется результирующий файл VOIDF.ASM, содержащий команды ассемблера.

    Листинг 1. VOIDF.CPP (скомпилируйте эту программу с опцией -S)

#include <stdio.h>

void f()
{ 
  int x = 123;
  printf("x == %d\n", x);
}

main()
{ 
  f();
  return 0;
};


    Замечание. Не стоить вводить или ассемблировать файл VOIDF.ASM. Вы сможете создать свою собственную немодифицированную копию с помощью компиляции листинга 1 с опцией -S. На диске файл VOIDF.ASM содержит листинг 2.

    Листинг 2. VOIDF.ASM

_TEXT segment byte public 'CODE'      ; начало сегмента кода 
;main()  
assume CS : _TEXT                     ;Директива ассемблера
_main proc near                       ;Начало функции main()
push bp                               ;Сохранить указатель базы 
mov bp,sp                             ;Задать bp равным sp
;{                            
;f(); 
call near ptr @f$qv                   ;Вызвать функцию f()
;return 0      
;} 
xor ax.ax                             ;Задать возвращаемое в ax значение равным 0
     pop bp                           ;Восстановить bp 
ret                                   ;Вернуться в место вызова
_main endp                            ;Конец функции main()   

;void f(void)
assume CS : _TEXT                     ;Директива ассемблера
@f$qv proc near                       ;Начало функции f()
push bp                               ;Сохранить регистр bp
mov bp, sp                            ;Задать bp равным sp 
sub sp,2                              ;Зарезервировать стек для х
;{
;int x= 123; 
mov word  ptr [bp-2], 123             ;Присвоить 123 целому х
;printf("x == %d\n", x);
push word  ptr [bp-2]                 ;Запомнить значение х
mov ax, offset DGROUP: s@             ;Запомнить  адрес строки s,
push ax                               ;хранящийся в сегменте данных
call near ptr _printf                 ;Вызвать функцию printf()
pop cx                                ;Удалить элемент из стека 
pop cx                                ;Удалить элемент из стека
;--Механизм удаления аргументов, которые запоминались в стеке перед вызовом,
;известен как соглашение вызова языка C. 
;}            
mov sp,bp                             ;Очистить указатель стека
pop bp                                ;Восстановить регистр bp 
ret                                   ;Вернуться в место вызова
@f$qv endp

?debug C E9                           ;Отладочная информация для
?debug C FA00000000                   ; Turbo Debugger
_TEXT ends 
_DATA  segment word public 'DATA'     ;Глобальный сегмент данных
s@ label byte                         ;Метка строки для printf()
db x = =%d                            ;строка для printf()
db 10                                 ;Символ новой строки
db 0                                  ;Нуль, завершающий строку 
_DATA ends                            ;Конец сегмента

_TEXT segment byte public 'CODE'      ;Сегменты поддерживаются в
_TEXT ends                            ;требуемом порядке
public _main                          ;Экспорт имени _main 
public @f$qv                          ;Экспорт функции f() 
extern _printf : near                 ;Импорт функции printf()
_s@ equ s@                            ;В этом коде не используется
end                                   ;Конец листинга
Тексты, приведенные в листингах 1 и 2, можно взять здесь.

    В сгенерированном тексте демонстрируются ключевые инструкции, которыми следует пользоваться при написании своих функций на ассемблере.


    Замечание. В соглашении вызова языка C в скомпилированной программе аргументы запоминаются в стеке перед вызовом функции, после чего вызывающий функцию обязан удалить аргументы из стека во избежание его переполнения. В альтернативном соглашении вызова языка Pascal аргументы из стека удаляет вызванная функция. Соглашение вызова языка C позволяет использовать переменное число аргументов в функции, но засоряет объектный код лишними инструкциями извлечения из стека. Соглашение вызова языка Pascal уменьшает размер кода, однако позволяет передавать функции только фиксированное число аргументов.

    Даже в этом простом примере за кулисами выполняется огромная работа! Следует понять, что "раскопки" в скомпилированном тексте требуют большого труда.


    На следующем шаге мы рассмотрим функции, возвращающие значениz.




Предыдущий шаг Содержание Следующий шаг