|
Ушел с форума
16383 / 7695 / 1080
Регистрация: 11.11.2010
Сообщений: 13,783
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 15.11.2013, 09:20 [ТС] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ГЛАВА 15 Ранние программы представляли собой бесконечные колонки двоичных чисел. Много позже программисты изобрели мнемонику для обозначения двоичных чисел эквивалентными псевдословами. Так из машинного кода произошел язык Ассемблер. Следующим шагом были придуманы макросы, призванные упростить написание программ за счет наиболее часто встречающихся фрагментов кода. Макросы (макроопределения) представляют из себя символическое имя, за которым стоит некий логически цельный набор инструкций. В тех местах программ, где должны встретиться подобные фрагменты, подставляются имена этих фрагментов, которые при компиляции автоматически подменяются соответствующими последовательностями команд. Поскольку при использовании макросов происходит простое механическое замещение их имен соответствующими фрагментами кода, то получается, что при таком подходе наиболее популярные макроопределения (которые могут встречаться в крупных программах сотнями) многократно тиражируются в теле программы, приводя к непомерному распуханию исполняемого кода. К счастью, на этой стадии прогресс тоже не остановился. Для решения проблемы достаточно было привязать блоки кода к определенному месту в памяти и обеспечить механизм передачи им управления в нужный момент. Так, собственно, и появились функции, они же процедуры, они же подпрограммы.ПОДПРОГРАММЫ-ПРОЦЕДУРЫ Функция — основная структурная единица процедурных и объектно-ориентированных языков. Функция представляет собой обособленную последовательность команд, вызываемую из различных частей программы. Функция может принимать один или несколько аргументов, а может быть без аргументов. Функция может возвращать результат своей работы (целочисленный результат или адрес, как правило, через регистр rax/eax/ax, вещественное число через st(0)), а может и ничего не возвращать. Ключевое свойство функции — по окончании работы функции управление возвращается на следующую инструкцию после вызова функции, характерный признак функции — многократный вызов функции из различных частей программы. Итак, чтобы избежать повторного выписывания часто повторяющихся групп команд, эту группу выписывают один раз, оформляют соответствующим образом, а затем в нужных местах просто передают управление на эти команды, которые поработав, возвращают управление обратно. Такая группа команд, которая решает некоторую подзадачу и которая организована таким образом, что ее можно использовать любое число раз и из любых мест программы, называется подпрограммой. При использовании подпрограмм основная программа становится более компактной. Кроме того использование подпрограмм облегчает поиск ошибок. Где размещать подпрограмму? Где угодно. При этом не нужно забывать, что сама по себе подпрограмма не должна выполняться, а может выполняться только тогда, когда к ней обращаются. Поэтому ее размещают таким образом, чтобы на нее случайно не попало управление, например, сразу же за командами JMP, RET. Если имеется несколько подпрограмм, то их располагают рядом. Обычно подпрограммы размещают:
Как оформлять подпрограмму? У директивы PROC есть параметр — это либо NEAR (близкий), либо FAR (дальний). Параметр может и отсутствовать, тогда считается, что он равен NEAR (в связи с этим параметр NEAR обычно не указывается). При параметре NEAR процедура называется близкой, при параметре FAR — дальней. К близкой процедура можно обращаться только из того сегмента, где она описана, а к дальней — из любого сегмента. Вызов процедур и возврат из них Существует два варианта CALL и RET для близких и дальних процедур.PROC и ENDP — псевдооператоры определения процедур. PROC определяет начало процедуры, а ENDP — ее конец. Метка перед каждой из процедур — это имя, которое мы ей присвоили. Достаточно только вставить имя процедуры в текст программы, а ассемблер присвоит необходимые адреса сам. Псевдооператоры NEAR и FAR предоставляют ассемблеру информацию о том как мы используем сегменты. Ассемблер применяет эту информацию при трансляции инструкции CALL. Существует два типа инструкций CALL и RET: близкие CALL NEAR и RETN и дальние CALL FAR и RETF. Дальний CALL вызывает процедуру, расположенную в другом сегменте. Ближний CALL вызывает процедуру из того же сегмента, в котором находится сам. Хорошая подпрограмма выполняет одну и только одну работу. Хорошая подпрограмма коротка, насколько это возможно, и велика, насколько это необходимо. Хорошая подпрограмма начинается не с кода, а с комментариев, описывающих ее назначение, результаты, ожидаемые входные данные и используемые регистры. В хорошей подпрограмме можно разобраться, не зная, что делает вся программа. Другими словами, хорошая подпрограмма коротка, изящна и точна.
В TASM после директивы PROC, а в MASM до директивы PROC следует имя подпрограммы, помечающее адрес первой команды (строка 38). Директива ENDP завершает подпрограмму. В TASM после директивы ENDP, а в MASM до директивы ENDP следует имя процедуры. В строке 16 главная программа вызывает подпрограмму с помощью команды CALL с именем процедуры. При исполнении команда CALL выполняет следующие действия:
Рис. 15.2.1. Вызов и возврат из подпрограммы Команда CALL (Вызов процедуры или задачи) CALL <операнд> Семантика команды:
Применение: как видно из описания алгоритма, команда CALL позволяет организовать гибкую и многовариантную передачу управления на подпрограмму с сохранением адреса точки возврата.
(Возврат ближний /дальний из процедуры = RETURN Near/Far from procedure) RET Семантика команды: возврат управления из процедуры вызывающей программе.RET <смещение> Алгоритм работы (работа команды зависит от типа процедуры):
Некоторые языки высокого уровня, к примеру Pascal, требуют, чтобы вызываемая процедура очищала стек от переданных ей параметров. Для этого команда RET содержит необязательный параметр число, который, в зависимости от установленного атрибута размера адреса, означает количество байт или слов, удаляемых из стека по окончании работы процедуры:
Нетрудно подсчитать количественные данные об экономии команд и потере времени при использовании процедур. Предположим, что группа команд занимает Количество потерянного времени равно общей длительности выполнения дополнительных команд CALL и RET ( Косвенный вызов процедур Предположим, что вы пишете программную оболочку наподобие«Far», «Волков» или «Norton Commander». Помните, как они устроены? Если вы нажали на функциональную клавишу
В случае косвенного вызова процедуры в качестве операнда имени процедуры указывается не сам адрес процедуры, а то место, где находится этот адрес. Таким местом может быть регистр общего назначения или слово памяти. По такой команде берется содержимое указанного регистра или слово памяти, оно рассматривается как адрес процедуры и по этому адресу передается управление. Косвенный вызов процедур используется в тех случаях, когда во время выполнения программы становится известно, какую процедуру необходимо выполнить:
Другие варианты команды CALL Основной вид команды CALL, когда в качестве операнда указывается имя процедуры. Но возможны все варианты операнда, используемого в команде безусловного перехода JMP.Примеры:
Первое: надо следить за согласованностью типа перехода с возвратом с типом возврата по команде RET, так как в этих случаях ассемблер уже не отвечает за такую согласованность. Второе: как и в команде JMP, при использовании в команде CALL ссылок вперед или косвенных ссылок следует уточнить тип этих ссылок (по умолчанию при ссылке вперед ассемблер транслирует близкий прямой вызов, а при косвенной ссылке — близкий косвенный вызов). Если вы вызываете процедуру FP, описанную как дальнюю, командой CALL FP, или вы вызываете процедуру командой CALL FAR PTR FP, транслятор генерирует последовательность команд PUSH CS и CALL FP.
Попытка напрямую оттранслировать команду «CALL FAR PTR ES:[BX]» вызывает ошибку транслятора «Argument to operation or instruction has illegal size». Попытка оттранслировать вот такую команду «CALL FAR PTR ES:[BX]» вызывает следующую ошибку транслятора «Illegal immediate». При трансляции же команды «CALL FAR PTR [BX]» будет сгенерирован код FF57FE, почему-то соответствующий команде «CALL [BX-2]», которая дальним вызовом не является, вероятнее всего это ошибка транслятора. Вывод — программируйте дальние вызовы в машинных кодах. Чтобы обмануть хакера, можно:
Также можно перед вызовом функции передать в качестве одного из аргументов адрес возврата, можно сохранять адрес возврата в специальной переменной и после завершения функции выполнить косвенный переход, используя эту переменную как операнд инструкции jmp и так далее... Передача параметров процедуре Нашей подпрограмме AddReg для хранения складываемых значений были необходимы четыре регистра: al,bl,cl,dl. Подпрограмма возвращала полученную сумму в регистре eax. Передача значений в подпрограмму через регистры является наиболее общим методом получения подпрограммами данных для обработки. Кроме передачи данных через регистры данные могут передаваться через глобальные переменные, а также через стек.Изменение глобальных переменных внутри процедур может привести к осложнениям, связанным с тем, что становится трудно определить, какое значение в какой процедуре было изменено. В сложной программе с большим количеством процедур, которые вызывают друг друга в различных последовательностях, подпрограммы, которые изменяют одни и те же глобальные переменные, могут вызвать ошибки, называемые эффектом наведения — когда одна подпрограмма вызывает другую подпрограмму, изменяющую глобальное значение, которое в данный момент УЖЕ используется для других целей. Хотя существуют исключения, передача параметров через стек или регистры предпочтительнее. Такой подход ведет к улучшению модульности кода и уменьшению вероятности нежелательных побочных эффектов. Передача параметров через регистры Иногда требуется передать процедуре фактические параметры. Простейший способ — передавать параметры через регистры: основная программа записывает фактические параметры в какие-то регистры, а процедура затем берет их оттуда и использует в своей работе. Результат процедура может также записать в какой-нибудь регистр, а основная программа затем извлекает его оттуда. Через какие регистры будут переданы параметры и через какие регистры будет возвращен результат — это личное дело автора программы, хотя эта проблема более подробно обсуждается в разделе «Модульное конструирование процедур».Передача параметров по значению Для примера вычислим d=min(a,b)+min(5,a-1). Вычисление функции min(x,y) опишем как процедуру, первый параметр передадим через регистр EAX, а второй — через EBX, результат вернем через регистр EAX:
Передача параметров по ссылке Пусть имеется процедура D с параметром x и пусть процедура производит следующее вычисление x=x/16. В основной программе есть обращения к нашей процедуре D(A) и D(B), где A и B имена переменных, значениями которых являются неотрицательные числа. Процедуре надо знать адреса ячеек A и B, в которые она должна сделать запись, и эти адреса обязана ей сообщить основная программа. Передача параметров по ссылке означает передачу адреса ячейки, соответствующей фактическому параметру. Передача адреса происходит через регистр — основная программа записывает в регистр адрес фактической переменной, а процедура берет его оттуда. Пусть для косвенной адресации в процедуре D мы выбрали регистр EBX, то есть к началу выполнения процедуры в регистре EBX будет находиться адрес той ячейки (A или B), содержимое которой процедура изменяет:
Сохранение регистров в процедуре Вашей процедуре при работе могут потребоваться любые регистры, но может ли процедура менять значения в регистрах? С большой вероятностью основной программе и процедуре могут потребоваться одни и те же регистры. Конечно, можно договориться, чтобы основная программа и процедура использовали разные регистры, однако на практике добиться этого достаточно сложно. Поэтому основную программу и процедуру не ограничивают в использовании регистров, но при этом требуют от процедуры, чтобы она сохраняла то значение в регистрах, которое туда записала основная программа. Достигается это тем, что в начале работы процедура помещает в стек значение тех регистров, которые ей понадобятся для работы, а перед выходом из процедуры из стека восстанавливаются прежние значения в регистрах (смотрите раздел «Модульное конструирование процедур»). Достаточно часто для этого используют команды PUSHA и POPA, сохраняя и восстанавливая из стека значения всех регистров, даже и неиспользуемых, «на всякий случай», так как в дальнейшем процедура может модифицироваться и начать работать с теми регистрами, которые в данный момент не используются. Сохранять значение регистра, через который процедура возвращает результат не надо, поскольку в изменении этого регистра и заключается цель работы процедуры.Передача параметров сложных типов Пусть происходит передача параметра по ссылке — при этом параметром является данное сложного типа (массив, структура и т.п.). Даже если процедура не меняет это данное, ей все равно обычно передается не само данное (для него может просто не хватить регистров), а его начальный адрес. Зная этот адрес, процедура может добраться до любой части этого данного.Пусть имеются массивы из чисел:
Передача параметров через стек Передача параметров через регистры — вещь удобная и используется очень часто. Однако параметров может быть много и регистров попросту не хватит. В таком случае используется передача параметров через стек: основная программа записывает фактические параметры (их значения или адреса) в стек, а процедура их оттуда извлекает.Изменим нашу подпрограмму AddReg, чтобы передавать ей параметры через стек:
Для правильного решения сначала сохраним адрес возврата, вытолкнем параметры в регистры eax, ebx, ecx и edx и затем снова вернем адрес возврата в стек:
Рис. 15.6.4 По команде RET число из стека удаляется адрес возврата, затем стек очищается на указанное операндом число байтов и для перехода по адресу возврата выполняются следующие действия:
Команда RET — это на самом деле команда RET 0, то есть возврат без очистки стека. Операнд команды RET указывает на сколько байтов, а не слов надо очищать стек. Для очистки стека от k параметров, каждый из которых имел размер двойного слова, был указан операнд 4*k. В операнде не должен учитываться адрес возврата — команда RET считывает его до очистки стека. C- и PASCAL-процедуры При передаче аргументов через стек командой PUSH можно пренебречь. Расширенный синтаксис инструкции CALL в TASMCALL имя_процедуры язык [, аргументы] Параметр команды CALL «язык» определяет способ передачи аргументов процедуре и способ очистки стека от переданных аргументов. В качестве «языка» допускается применение ключевых слов: C, CPP, PASCAL, BASIC, PROLOG или NOLANGUAGE. Если тип языковых соглашений не задан, то используется тип, указанный в директиве MODEL или явное указание типа языковых соглашений в объявлениях PROC или PUBLIC перекрывает для данного идентификатора тип, назначенный директивой MODEL. В языках PASCAL, BASIC и PROLOG аргументы процедуры будут переданы в стек в том же порядке, в котором они указываются в операторе вызова процедуры, а в языках C/C++ используется обратный порядок. Языковые соглашения определяют порядок очистки стека от переданных аргументов. В языках C/C++ это возлагается на вызывающую процедуру, а во всех остальных на вызываемую. Следующая программа, написанная под TASM, вызывает одну и туже процедуру t1 с параметром PASCAL и C. Процедура t1 закончена директивой RET, а компилятор, в зависимости от языкового соглашения, сам подставит RETN/F или RETN/F число:
MASM не позволяет записать аргументы CALL через запятую — для этого в MASM есть директива INVOKE. Для использования INVOKE требуется первоначально определить прототип процедуры, то есть указать количество аргументов и их тип: имя_ процедуры PROTO [расстояние] [язык] [, [параметр]:тип]... Скомпилированные программы будут отличаться в зависимости от параметров директивы model, proto, proc или от ключей, использованных при компиляции (/Gc или /Gd). Следующая программа использует соглашение о стиле вызова функций как в FORTRAN или в PASCAL:
Локальные данные процедуры Локальные данные процедуры — это величины, используемые только на время выполнения процедуры. Обычно локальные данные размещают в регистрах, но если в процедуре много локальных данных, регистров может не хватить, а размещать локальные данные в памяти не рационально, так как после выполнения процедуры это место в памяти будет расходоваться напрасно. Поэтому для локальных данных выделяют место в стеке — при входе в процедуру в вершине стека захватывается нужное число байт для локальных данных, а перед выходом из процедуры это место освобождается. Для этого в стеке запоминается текущее значение регистра EBP, а затем в регистр EBP устанавливается значение ESP, уменьшенное на число байт, требуемых для локальных данных. Например, процедуре P требуется 6 байт (4 байта под локальную переменную A и 2 байта под локальную переменную B):
Рекурсивные процедуры Иногда алгоритм определяется в собственных терминах, другими словами, алгоритм вкладывает сам в себя таким образом, что вычислительная процедура вызывает саму себя. Например, алгоритм Гонера вычисления полинома ((…(anx+an-1)x+an-2)x+…)x+a1)x+a0 включает в себя перемножение x и an, прибавление к результату an-1, умножение на x полученного результата, прибавление к новому результату an-2 и так далее. Следующий результат получается путем умножения предыдущего результата на x и прибавление очередного коэффициента. Однократное применение алгоритма состоит в умножении и сложении, а окончательный результат получается вложением простого алгоритма умножения-сложения в самого себя.Программа при вычислении полинома по алгоритму Гонера должна будет сохранять только коэффициенты многочлена и значения аргумента только одной промежуточной величины. Подпрограммы, которые при вычислении вызывают сами себя, называются рекурсивными и иногда требуются в программных системах для поиска, декодирования синтаксиса языка высокого уровня и решения других задач. Рекурсивные алгоритмы допускают реализацию в виде процедуры, которая вызывает сама себя. Но при этом необходимо обеспечить, чтобы каждый последовательный вызов не разрушал параметров и результатов, полученных предыдущим вызовом, и чтобы процедура не модифицировала сама себя. Это означает, что каждый вызов должен запоминать свой набор параметров, регистры и все промежуточные результаты в различных областях памяти. Чтобы гарантировать использование таких отдельных областей памяти, обычно применяется стек. При включении данных в стек их можно удобно считывать в обратном порядке по мере того, как процесс выходит из вложений. Запоминаемые при подобной организации процедуры данные называются кадром, и простой доступ к отдельным элементам в кадре реализуется через регистр ESP или EBP. Обычно кадр хранит запомненное содержимое регистров, адреса параметров и рабочую (временную) область памяти. Правильное запоминание информации в стеке обеспечивает упорядоченный выход. Для предотвращения бесконечного вложения рекурсивный алгоритм должен включать в себя условный переход, который в конце концов прекращает вложение. Ниже даны примеры рекурсивных процедур для вычисления чисел Фибоначчи и для вычисления факториала.
Разложение в ряд Хотя команды сопроцессора во многом облегчают жизнь программиста, пишущего на языке ассемблера, но эти команды, во-первых, медленнее целочисленных команд сложения, вычитания и сдвига, а, во-вторых, конструкторы сопроцессора все равно не смогли реализовать все математические функции на одном кристалле (например, команда извлекающая квадратный корень есть, а команды извлекающей кубический корень — нет). Поэтому рано еще отказываться от тех средств, которыми широко пользовались в «досопроцессорные» времена, например, от разложения функции в ряд.Пусть x — любое число, а f(x) — некоторая функция, зависящая от x, непрерывная и имеющая все производные при x = a, тогда f(x) можно представить в вид суммы степенного ряда, получающегося из формулы Тейлора: Теперь, для примера, попробуем вычислить кубический корень по приближенному алгоритму разложения в ряд Тэйлора. Пусть поэтому Ближайшее к 100 число 125 с известным кубическим корнем 5. Куб величины Модульное конструирование процедур Создание процедур в виде готовых модулей предполагает не «изобретение велосипеда» каждый раз при написании новой программы, а построение новых программ из уже готовых (отлаженных, проверенных и оптимизированных) модулей. Следующие рекомендации должны помочь вам при оформлении процедур в виде готовых модулей. Если хотите, определите свои рекомендации, но, в любом случае, все время придерживайтесь одного и того же. Ваша работа значительно упростится, если вы будете последовательными.
Таким образом, получается, что надо обеспечить подачу, скажем, всего четырех различных напряжений вместо того, чтобы к каждому блоку подавать свое определенное напряжение. Модульное конструирование и стандартные интерфейсы в языке ассемблера не менее важны, и именно поэтому я рекомендую использовать их в дальнейшем. Как вы сами увидите, эти правила значительно упростят ваше программирование. Давайте рассмотрим эти правила детальнее. Сохраняйте и восстанавливайте всегда все регистры (их значения), кроме тех случаев, когда процедура передает значение в регистре. У микропроцессора не так много регистров. Сохраняя значения регистров в начале процедуры, вы освобождаете регистры для использования в самой процедуре. Но при этом необходимо быть весьма осторожным. Делайте это в начале процедуры с помощью команды PUSH, а командой POP восстанавливайте значения регистров в конце процедуры. Единственное исключение составляют процедуры, которые должны вернуть некоторую информацию процедурам, их вызывающим. Например, процедура, считывающая символ с клавиатуры, должна как-то вернуть этот символ. Не сохраняйте в стеке те регистры, которые собираетесь использовать для возвращения информации. Короткие процедуры также помогают в решении проблемы сокращения числа используемых регистров. Старайтесь писать процедуры, которые используют только один регистр. Это не только помогает сократить число используемых регистров, но также делает программу более легкой для написания и часто облегчает ее чтение и отладку. Будьте последовательными в использовании для передачи информации одних и тех же регистров. Работа станет легче, если вы установите стандарты для обмена информацией между процедурами. Применяйте один регистр для передачи, а другой для получения информации. Вам также необходимо посылать адреса массивов информации и для этого старайтесь использовать регистр EBX. Резервируйте регистр ECX для использования в качестве счетчика повторений. Старайтесь использовать регистр ECX всегда, когда вам понадобится счетчик повторений или когда вы захотите вернуть результат какого-либо подсчета, например, числа символов, считанных с клавиатуры. Наконец, устанавливайте флаг переноса (CF) всякий раз, когда происходит ошибка. Определяйте все внешние взаимодействия процедуры в заголовке-комментарии. Нет необходимости знать, как работает процедура, если все что вы хотите с ней делать, это использовать ее, поэтому помещайте перед каждой процедурой детальный комментарий-заголовок. Этот заголовок содержит всю информацию, которую необходимо знать пользователю. Он говорит о том, что именно необходимо помещать в каждый регистр перед вызовом процедуры, а также говорит, какую информацию процедура возвращает. Большая часть процедур использует регистры для хранения своих переменных, но некоторые из процедур используют переменные, хранящиеся в памяти. Комментарий-заголовок должен сказать, какие из этих переменных только считываются, а какие изменяются. И, наконец, каждый заголовок должен содержать список вызываемых процедур. Ниже приведен пример такого заголовка:
Эти правила упрощают программирование на языке ассемблера, и вы, конечно, будете их придерживаться, но необязательно с первой попытки написания процедуры — в этом просто нет необходимости. Первая версия процедуры или программы лишь пробный вариант. Часто программист не знает точно, как написать программу, которую он держит в голове, поэтому в черновом варианте пишите программу, не придерживаясь законов модульного конструирования. Сделайте примерный набросок будущей программы и убедитесь, что она работает. Затем вернитесь назад и перепишите каждую процедуру, чтобы она отвечала рекомендациям, описанным в этой главе. Кодировка CALL и RET Внешние процедуры Библиотеки объектных модулей (link library) представляют собой файл, содержащий процедуры, предварительно скомпилированные в машинный код. С точки зрения программиста, библиотечный код ничем не отличается от одного или нескольких исходных файлов с программой, написанной другими программистам и содержащей процедуры, константы и переменные. Эти исходные файлы компилируются в объектные файлы, которые, в свою очередь, помещаются в библиотеки.При компиляции программы ассемблер сгенерирует соответствующую команду вызова процедуры, однако поле адреса этой процедуры он оставит незаполненным. На этапе компиляции адрес внешней процедуры (так называется процедура, находящаяся в библиотеке объектных модулей) не известен ассемблеру. Адрес определяется компоновщиком на этапе сборки исполняемого файла. Компоновщик находит указанную процедуру в указанной программистом библиотеке объектных модулей, копирует ее машинный код в исполняемый файл пользователя и прописывает адрес этой процедуры в тех местах программы, где эта процедура вызывается с помощью команды CALL. В идеологии работы приложений под управлением Windows и DOS имеются коренные различия. При написании DOS-программ обращение к операционной системе осуществляется посредством вызова различных программных прерываний (обращение к DOS через прерывание 21h, обращение к BIOS через прерывание 10h, обращение к клавиатуре через прерывание 16h и так далее), а также напрямую (через порты ввода-вывода) к ресурсам компьютера. Программы, написанные под Windows, непосредственно к ресурсам компьютера никогда не обращаются. Для реализации взаимодействия с многозадачной операционной системой Windows используется интерфейс, базирующийся на вызове функций, специально определенных в операционной системе. Это множество системных функций (более 2500) называется интерфейсом прикладного программирования Windows (Windows Application Programming Interface — Windows API). Функции Windows API включают в себя все необходимые системно-зависимые действия, такие как выделение памяти, вывод на экран и печать, создание окон и тому подобное. Кроме этого, функции Windows API позволяют при программировании не учитывать специфику конкретного оборудования. Программное обеспечение, которое поддерживает аппаратную независимость, содержится в DLL-файлах. С точки зрения программирования, функции Windows API аналогичны «обычным» функциям. Они имеют набор входных и выходных параметров, иногда возвращают через регистр eax в вызвавшую их программу некоторое значение. Так как функций Windows API более двух тысяч, можно предположить, что каждая программа для операционной системы Windows должна быть связана с большим количеством программных библиотек, что должно приводить к дублированию большого объема кода. Поэтому библиотеки функций Windows API объединены в DLL-файлы, доступ к которым может получить любая программа во время выполнения. В процессе компиляции, когда программа вызывает функцию Windows API, компоновщик не добавляет код этой функции к исполняемому модулю. Вместо него он добавляет только инструкции для загрузки функции из DLL-файлов, поэтому ваши файлы будут содержать только имена Windows API-функций и имена DLL, в которых эти функции находятся. При выполнении программы все необходимые функции Windows API также загружаются в память. Таким образом, при построении программы код функций Windows API фактически не используется — он добавляется только тогда, когда программа загружается в память для выполнения. Наши программы под Windows будут состоять из нескольких модулей. Один модуль (файл с расширением asm) мы будем писать сами. В другом модуле (файл с расширением inc — include — подключенный) объявлены стандартные константы и типы данных. Третий модуль (файл с расширением lib — library — библиотека) сообщит транслятору, из каких системных библиотек нужно взять Windows API-функции. Когда ассемблер транслирует asm-модуль, то он еще ничего не знает о dll-модулях (описанных в lib-файле) и именах функций, описанных в dll-модулях. Поэтому, встретив в asm-модуле имена API функций и не найдя их описания в этом файле, ассемблер зафиксирует ошибку «неописанное имя». Что-бы не было такой ошибки, ассемблеру сообщают, что имена функций хотя и используются в asm-модуле, но описаны все же в другом модуле, откуда они будут взяты. Такое сообщение делается с помощью директивы EXTERN (external — внешний), имеющей следующий вид: EXTERN <имя>:<тип>, … , <имя>:<тип> Директива EXTERN может указываться любое число раз и в любых местах модуля. Для связи asm-модуля с другими модулями необходимо использовать директиву INCLUDE (включить). В общем случае обращение к директиве INCLUDE имеет следующий вид:INCLUDE <имя файла> Имя файла должно быть заключено в угловые скобки, если оно содержит символы: «/» (прямой слэш), «;» (точка с запятой), «>» (больше), «<» (меньше), «‘» (одиночная кавычка), «”» (двойная кавычка).Директива INCLUDELIB сообщает ассемблеру, что текущий модуль должен быть связан с библиотекой, имя которой задано параметром имя библиотеки INCLUDELIB <имя библиотеки> Строка includelib user32.lib — это сообщение транслятору, что наша программа должна быть связана с библиотекой user32.lib. Основные системные библиотеки обычно находятся в папке Windows\System и содержат большое количество библиотек функций Windows API. В число основных входят три библиотеки:
Контрольные вопросы и упражнения
6
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 15.11.2013, 09:20 | |
|
Какой учебник выбрать для изучения ассемблера? Подскажите, пожалуйста,учебник по асму. Учебник MASM32(под win) Посоветуйте учебник и IDE Подскажите учебник по Ассемблеру Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |
|
Новые блоги и статьи
|
|||
|
Генератор паролей (вариант №3)
Maks 30.10.2026
Финальная версия генератора паролей 1С.
Доработка решения в этот раз коснулась только визуальной части, а именно, добавлен реквизит ДопПараметры (тип Булево), который скрывает/ отображает реквизиты:. . .
|
Магическое мышление и магия, современная наука не отличима от магии (говорит ИИ Джемени)
zorxor 30.10.2026
Джемени, расскажи, пожалуйста, что такое магическое мышление, что такое магия, почему современная наука чем-то напоминает магию?
Фраза «Любая достаточно развитая технология неотличима от магии». . .
|
За какое время ИИ может подобрать пароль по хешу (рассказывает Джемени ИИ)
zorxor 30.10.2026
Расскажи, Джемени, если известен хэш МД5, например, за сколько ИИ сможет подобрать соответствующий пароль?
Искусственный интеллект (ИИ) может подобрать простой или средний по сложности пароль по. . .
|
Кто убил Suno? Почему наше солнышко погасло? (диалог с ИИ Джемени)
zorxor 30.10.2026
Как убивали Suno. Хронология.
«Убийство» музыкальной нейросети Suno — это затяжной процесс, в котором технологический хайп стартапа разбился о жесткую реальность судебных исков от гигантов. . .
|
|
Генератор паролей (вариант №2)
Maks 30.10.2026
Обновленная версия ранее созданной обработки.
Добавлены следующие реквизиты типа Булево:
- Буквы;
- Цифры;
- Символы.
Изменена логика команды "Сгенерировать" и процедуры "ПриОткрытии":. . .
|
Генератор паролей (вариант №1)
Maks 29.10.2026
Реализация из решения ниже выполнена исключительно по моей инициативе, дабы облегчить себе работу в создании паролей для администрируемых мною сервисов и пользователей.
Самым главным критерием для. . .
|
Установка ChatGPT (Codex) на Windows без использования Microsoft Store
Fabien159 28.10.2026
Здравствуйте всем читающим! Опишу процесс установки ChatGPT (Codex) на Windows, если по каким-то причинам у вас не получается устанавить его через Microsoft Store.
И так:
1. Скачиваем с. . .
|
Программное добавление префикса в нумерацию документов
Maks 27.10.2026
Реализация из решения ниже выполнена в самописной конфигурации на БСП (помощь начинающему коллеге).
Задача:
Программно добавить префикс в нумерацию документов.
Решение:
В модуле объекта. . .
|