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