    57:	_TEXT ends 
    58:	_DATA segment word public 'DATA'	;Глобальный сегмент данных 
    59:	s@ label byte			;Метка строки для printf() 
    60:	db 'x == %d'			;строка для printf() 
    61:	db 10				;Символ новой строки 
    62:	db 0				;Нуль, завершающий строку 
    63:	DATA ends	Конец сегмента 
    64:	TEXT segment byte public 'CODE'	;Сегменты поддерживаются в 
    65:	_TEXT ends			;требуемом порядке 
    66:	public _main			;Экспорт имени jnain 
    67:	public @f$qv			;Экспорт функции f() 
    68:	extrn _printf: near			;Импорт функции printf() 
    69:	_s@ equ s@			;В этом коде не используется            
    70:	end				;Конец листинга

    В сгенерированном тексте демонстрируются ключевые инструкции, которыми 
следует пользоваться при написании своих собственных функций на ассемблере. 
Функция main() начинается в строке б с директивы ргос, которая присваивает 
метку _main этой ячейки памяти с ближним адресом. (В других моделях памяти 
адрес может быть дальним.) В ассемблере, такие глобальные имена, как main, 
имеют лидирующий знак подчеркивания (_main). В строке 7 значение регистра bр 
сохраняется в стеке, затем ему присваивается значение регистра sp. Регистр bр 
адресует элементы, запомненные в стеке, и обычно внутри функции 
инициализируется значением стекового указателя. 
    В строке 13 вызывается функция программы f(), заданная ближним адресом метки 
@f$qv. Компилятор сам создает подобные метки в случае необходимости. Обратите 
внимание, что исходной текст C++ отражается в комментариях строк 9-12. Эти 
комментарии приводятся над соответствующим участком ассемблерного кода. 
    В строках 17-19 реализуется оператор возврата из функции main(). Операция 
исключающего ИЛИ (хог), выполняемая над регистром ах в качестве аргументов, 
очищает этот регистр, устанавливая нулевое возвращаемое значение (строка 15). 
Инструкция jmp в строке 17, очевидно, бесполезна — она приведет к простому 
переходу к следующей инструкции. Это демонстрирует, что в результат работы 
компилятора могут иногда вкрасться бессмысленные инструкции. В критических 
случаях вы можете оптимизировать такие операторы, переписав их на ассемблере 
(как — вы узнаете дальше в этом разделе). 
    Наконец, функция main() завершается в строках 23, 24, восстанавливая
сохраненное первоначальное значение bр (отменяя воздействие строки 7) и 
возвращаясь в место вызова функции main() — начальный код запуска, который 
компонуется вместе со скомпилированным модулем при создании результирующих 
исполняемых файлов с расширением .ЕХЕ. 
    Теперь мы подошли к функции f() (строки 30-54). Поскольку остальные функции 
тоже могут воспользоваться регистром bр, первым делом необходимо запомнить его 
в стеке (строка 31). После этого для адресации в этой функции локальных 
переменных и параметров (в данном случае они отсутствуют) регистру bр 
присваивается значение стекового указателя sp (строка 32). Точно такие же 
действия имели место в начале функции main(). Большинство функций начинаются 
аналогичным образом. 
    В строке 33 стековый указатель "понижается" на два, тем самым 
резервируется указанное количество байтов в стеке для локальной целой 
переменной х, принадлежащей функции f() (строка 36). Инструкция mov в строке 38 
запоминает литеральное значение 123 в этом зарезервированном участке памяти. 
Обратите внимание на использование bр для адресации локальных переменных в 
операторах. Далее следует оператор вызова функции printf() в строках 42—47. В 
коде значение переменной х записывается в стек вместе с адресом строки s, 
хранящейся в сегменте данных программы. Это литеральная строка, использованная 
в модуле VOIDF.CPP (строки 14 глистинга 17.1 и 40 листинга 17.2). Компилятор 
собирает все такие строки и запоминает их в сегменте данных программы. Он 
также изобретает метки, подобные s, для адресации безымянных объектов. 
    Запомнив соответствующие аргументы в стеке, функция далее вызывает функцию 
printf() в строке 45. По соглашению имя этой библиотечной функции на ассемблере 
звучит _printf. По возвращении из функции printf() две инструкции pop в строках 
46 и 47 удаляют аргументы, которые запоминались в стеке перед вызовом, этот 
механизм известен как соглашение вызова языка С. 

    ЗАМЕЧАНИЕ 

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


    Глава 17. Оптимизация с помощью ассемблера     441 
