Пятница, 18.07.2025, 09:27 Приветствую Вас Гость

On-line: Книги, учебники, статьи

Главная | Регистрация | Вход | RSS

Глава 7. Использование ассемблера с языками высокого уровня

Глава 7. Использование ассемблера с языками высокого уровня

Данная глава посвящена вопросам использования ассемблера с языками высокого уровня. К сожалению, многие современные программисты, не зная языка ассемблера или не зная, как его использовать с языками высокого уровня, лишены мощного и гибкого инструмента программирования. Я бы сказал так: специалист по программированию на любом языке программирования должен владеть ассемблером как вторым инструментом. Это похоже на то, что изучение европейских языков в идеале должно предваряться изучением основ латыни.

Вообще, стыковка ассемблера с языками высокого уровня зиждется на трех китах: согласование имен, согласование параметров, согласование вызовов. Остановимся сначала на последнем "ките", т.е. на согласовании вызовов.

Согласование вызовов. В операционной системе MS DOS вызываемая процедура могла находиться либо в том же сегменте, что и команда вызова, тогда вызов назывался близким (NEAR) или внутрисегментным, либо в другом сегменте, тогда вызов назывался дальним (FAR) или межсегментным. Разница заключалась в том, что адрес в первом случае формировался из двух байт, а во втором - из четырех байт. Соответственно, возврат из процедуры мог быть либо близким (RETN), т.е. адрес возврата формировался на основе двух байт, взятых из стека, либо дальним (RETF), и в этом случае адрес формировался на основе четырех байт, взятых опять же из стека. Ясно, что вызов и возврат должны быть согласованы друг с другом. В рамках единой программы это, как правило, не вызывало больших проблем. Но вот когда необходимо было подключить или какую-то библиотеку, или объектный модуль, здесь могли возникнуть трудности. Если в объектном модуле возврат осуществлялся по RETN, вы должны были компоновать объектные модули так, чтобы сегмент, где находится процедура, был объединен с сегментом, откуда осуществляется вызов. Вызов в этом случае, разумеется, должен быть близким (см. [1]). Если же возврат из процедуры осуществлялся по команде RETF, то и вызов этой процедуры должен быть дальним. При этом при компоновке вызов и сама процедура должны были попасть в разные сегменты. Данная проблема усугублялась еще и тем, что ошибка обнаруживалась не при компоновке, а при исполнении программы. С этим были связаны и так называемые модели памяти в языке Си, что также было головной болью многих начинающих программистов. Если, кстати, Вы посмотрите на каталог библиотек Си для DOS, то обнаружите, что для каждой модели памяти там существовала своя библиотека. Сегментация памяти приводила в Си еще к одной проблеме - проблеме указателей, но это уже совсем другая история. В Турбо Паскале пошли по другому пути. Там приняли, что в программе должен существовать один сегмент данных и несколько сегментов кода. Если же Вам не хватало одного сегмента для хранения данных, то предлагалось использовать динамическую память. При переходе к Windows мы получили значительный подарок в виде плоской модели памяти. Теперь все вызовы по типу являются близкими, т.е. осуществляющимися в рамках одного огромного сегмента. Тем самым была снята проблема согласования вызовов, и мы более к этой проблеме обращаться не будем.

Согласование имен. Согласование вызовов, как мы убедились, снято с повестки дня, а вот согласование имен год от года только усложнялось. Кое-что Вы уже знаете. Транслятор MASM, как известно, добавляет в конце имени @N, где N количество передаваемых в стек параметров. То же делает и компилятор Visual C++. Т.о. трудности возникают уже при согласовании двух ассемблерных модулей. В этом смысле TASM является более гибким компилятором, т.к. при желании в конце любого имени можно добавить @N, тем самым согласовав имена.

Другая проблема - подчеркивание перед именем. Транслятор MASM генерирует подчеркивание автоматически, если в начале программы устанавливается тип вызова stdcall (Standard Call, т.е. стандартный вызов). Транслятор TASM этого не делает, следовательно, при необходимости это нужно делать прямо в тексте программы, что, на мой взгляд, является положительным моментом. Интересно, что между фирмами Borland и Microsoft здесь полное разночтение.

Еще одна проблема - согласование заглавных и прописных букв. Как Вы помните, при трансляции с помощью TASM мы используем ключ /ml как раз для того, чтобы различать буквы прописные и заглавные. Транслятор MASM делает это автоматически. Как известно, и в стандарте языка Си с самого начала предполагалось различие между заглавными и прописными буквами. В Паскале же прописные и заглавные буквы не различаются. В этом есть своя логика: Турбо Паскаль и Delphi не создают стандартных объектных модулей, зато могут подключать их. При создании же динамических библиотек туда помещается имя так, как оно указано в заголовке процедуры.

Наконец последняя проблема, связанная с согласованием имен, - это уточняющие имена в Си++. Дело в том, что в Си++ возможна так называемая перегрузка. Это значит, что одно и тоже имя может относиться к разным функциям. В тексте программы эти функции отличаются друг от друга по количеству и типу параметров и типу возвращаемого значения. Поэтому компилятор Си++ автоматически делает в конце имени добавку - так, чтобы разные по смыслу функции различались при компоновке. Разумеется, фирмы Borland и Microsoft и тут не пожелали согласовать свои позиции и делают в конце имени совершенно разные добавки. Обойти эту проблему не так сложно, нужно использовать модификатор EXTERN "С" (см. примеры далее).

Согласование параметров. В таблице ниже представлены основные соглашения по передаче параметров в процедуру. Заметим в этой связи, что во всех наших ассемблерных программах мы указывали тип передачи параметров как stdcall. Однако, по сути, это никак и нигде не использовалось - так передача и извлечение параметров делалась нами явно, без помощи транслятора. Когда мы имеем дело с языками высокого уровня, это необходимо учитывать и знать, как работают те или иные соглашения.

Таблица, представляющая соглашения о вызовах
СоглашениеПараметрыОчистка стекаРегистры
Pascal (конвенция языка Паскаль)Слева направоПроцедураНет
Register (быстрый или регистровый вызов)Слева направоПроцедураЗадействованы три регистра (EAX,EDX,ECX), далее стек
Cdecl (конвенция С)Справа налевоВызывающая программаНет
Stdcall (стандартный вызов)Справа налевоПроцедураНет

Таблица довольно ясно объясняет соглашения о передаче параметров, и здесь более добавить нечего.

Остановлюсь еще на весьма важном моменте - типе возвращаемых функцией данных. С точки зрения ассемблера здесь все предельно просто: в регистре EAX возвращается значение, которое может быть либо числом, либо указателем на некую переменную или структуру. Если возвращаемое число типа WORD, то оно содержится в младшем слове регистра EAX. Однако имея дело с Си, Вам надо очень аккуратно обращаться с такой проблемой, как преобразование типов. Преобразование типов — это целая наука, на которой мы не можем останавливаться в данной книге.

I

В данном разделе рассматривается простой модуль на языке ассемблера, содержащий процедуру, копирующую одну строку в другую. Мы подсоединяем этот модуль к программам, написанным на языке Си и Паскаль, с использованием трех трансляторов: Borland C++ 5.02, Visual C++ 6.0, Delphi 5.0.

1) Borland C++ 5.0

Функцию, вызываемую из модуля, написанного на языке ассемблера, мы объявляем при помощи модификаторов extern "С" и stdcall. Поскольку модуль на языке ассемблера транслируется с помощью транслятора TASM, проблемы с подчеркиванием не возникает. Тип вызова stdcall предполагает, что стек освобождается в вызываемой процедуре. В ассемблерном модуле вызываемая процедура должна быть дополнительно объявлена при помощи директивы PUBLIC.

// файл copyc.cpp
#include <windows.h>
#include <stdio.h>
extern "C" _stdcall COPYSTR(char *, char *);
void main()
{
 char s1[100];
 char *s2="Privet!";
 printf("%s\n",(char *)COPYSTR(s1, s2));
 ExitProcess(0);
}

; файл copy.asm
.386P
; эта процедура будет вызываться из внешнего модуля
PUBLIC COPYSTR
; плоская модель
.MODEL FLAT, stdcall
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
; процедура копирования одной строки в другую
; строка, куда копировать [EBP+08Н]
; строка, что копировать [EBP+0CН]
; не учитывает длину строки, куда производится копирование
COPYSTR PROC
 PUSH EBP
 MOV EBP,ESP
 MOV ESI,DWORD PTR [EBP+0CH]
 MOV EDI,DWORD PTR [EBP+08H]
L1:
 MOV AL,BYTE PTR [ESI]
 MOV BYTE PTR [EDI],AL
 CMP AL,0
 JE L2
 INC ESI
 INC EDI
 JMP L1
L2:
 MOV EAX,DWORD PTR [EBP+08H]
 POP EBP
 RET 8
COPYSTR ENDP
_TEXT ENDS
END
Вход на сайт
Поиск
Календарь
«  Июль 2025  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей
Наш опрос
Как Вам удобнее??
Всего ответов: 341
Мини-чат
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0