http://sulfurzona.com/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Forum
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
 

Паскаль для новичков (часть 19)

 

Распределение памяти для локальных переменных

 
Паскаль для новичковВ системе Turbo Pascal существует три вида памяти: сегмент данных, стек и хип. Сегмент данных не может превышать 65520 байт, и в нём размещаются все глобальные переменные и типизированные константы. Размер сегмента данных устанавливает сам компилятор при компиляции программы, исходя из суммарного размера памяти, занимаемой глобальными переменными программы, включая глобальные переменные используемых модулей. Если общий размер всех переменных, описанных в глобальном блоке программы и типизированных констант, превышает 65520 байт, то компилятор выдаст сообщение “Error 96: Too many variables.” (слишком много переменных).
 
О хипе(heap) я расскажу позднее. Стек – это область данных, размер которой не может превышать 64К и может устанавливаться директивой $M. Минимально допустимый размер стека – 1024 байта, а по умолчанию он равен 16384 байтам. Устанавливая размер стека, следует учитывать то, что модуль SYSTEM требует под свои нужды целых 522 байта стека, следовательно, если установлен размер стека 1024 байта, то из них реально доступны только 1024 – 522 = 502 байта.
 
В предыдущих публикациях я уже говорил о том, что локальные переменные размещаются в стеке и что нужно быть осторожным при использовании рекурсивных алгоритмов. Что же происходит при вызове подпрограммы? Если подпрограмма при вызове должна получить параметры, то они помещается в стек. Затем выполняется машинная команда вызова подпрограммы (Call). Эта команда помещает в стек адрес машинной команды (4 байта = сегмент : смещение если длинный вызов Far или 2 байта = смещение в пределах текущего сегмента памяти если вызов ближний Near), следующей за командой вызова, чтобы при возвращении из подпрограммы извлечь из стека адрес команды, перед которой был вызов на процедуру и продолжить выполнение программы.
 
Сразу после выполнения машинной команды вызова подпрограммы начинается выполнение команд этой подпрограммы, но предварительно производится отведение области стека, необходимого размера для локальных переменных, и если установлена директива {$S+} (включена по умолчанию), то выполняется системная процедура проверки переполнения стека. Если же эта директива отключена {$S–}, то никаких проверок производиться не будет, что даст небольшой прирост производительности программы, но может привести к краху системы и понадобиться всеми “любимая” клавиша Reset. Поэтому, если всё же есть необходимость такой рискованной оптимизации, то совсем не обязательно отключать проверку стека для кода всей программы, а лишь для процедур, которые необходимо оптимизировать, и их можно поместить в конструкцию директив {$S–}…{$S+}.
 
После завершения выполнения кода подпрограммы перед возвращением управления производится освобождение отведённой области стека. Потом из стека извлекается адрес команды, следующей за вызовом подпрограммы, и которой следует передать управление. В зависимости от типа вызова процедуры, извлекается адрес длиной 4 байта при вызове Far или длиной 2 байта при вызове Near. Управление передаётся команде, следующей за командой вызова.
 
С вызовом подпрограммы кажется всё понятно. Теперь подробно рассмотрим вопрос, касающийся отведения памяти стека под параметры и локальные переменные. При описании интерфейса подпрограммы следует учитывать тот факт, что все машинные команды, осуществляющие работу со стеком (занесение и извлечение данных) являются 16-разрядными и следовательно могут оперировать сразу двумя байтами. Таким образом, если процедура или функция получает параметр типа word или integer, то в стеке под такой параметр будет отведено 2 байта, что вполне логично. Если один из параметров имеет тип longint, single или pointer или параметр передан по ссылке или является бестиповым, то в стеке отводится под этот параметр 4 байта, что тоже вполне логично.
 
Что касается параметров, имеющих размер более 4 байт, то под них в стеке выделяется область размером, округлённым до большего числа, кратного двум, т.е. если вы пожелали передать процедуре параметр-значение в виде массива размером 10001 байт (array [0..10000] of byte), то размер отводимой области будет равен 10002 байтам. Такие затраты памяти можно считать вполне допустимыми. Но есть один нюанс, касаемо параметров типа byte, char и shortint. В виду того, что в стек нельзя поместить однобайтное значение, то под каждый параметр размером один байт в стеке отводится 2 байта. Т.е. если подпрограмма получает два параметра типа byte, то в стеке вместе они будут занимать 4 байта, т.е. по 2 байта каждый. Пока это всё, что касается параметров.
 
Теперь, относительно отведения области стека под локальные переменные. Всё, что касается переменных, имеющих размер 2 и более байт, выполняется в соответствии с описанным выше. А вот что касается однобайтных переменных, так если в описании Var указаны подряд несколько переменных, например типа byte, то под них будет экономично отведена область, по одному байту на каждую переменную. Если же в описании Var описана только одна переменная типа byte или помимо списка таких переменных ещё одна переменная описана отдельно, например:
 
Procedure
Var A, B, C : byte;
        J : word;
       D : shortint;
Begin
… 
то под такую переменную (в данном случае D) будет отведено 2 байта, что тоже логично, но не экономично. А под три переменные A, B и C будет отведено 4 байта, так как A и B будут размещены как одно слово стека, а для C пары нет, и она займёт ещё 2 байта. Поэтому, в данном случае целесообразнее описать переменные так:
Procedure
Var A, B, C : byte;
        D : shortint;
        J : word;
Begin
что приведёт к эффективному расходованию стека, так как теперь на каждую из A, B, C и D переменных будет в стеке отведено по одному байту.
Таким образом, если подпрограмма вызывает другую подпрограмму, а та в свою очередь третью, то размер отведённой области стека под параметры и локальные переменные растёт лавинообразно как снежный ком. Если вложенность процедур не значительна, то можно спрогнозировать необходимый размер стека. Но если в программе используются рекурсивные вызовы подпрограмм, то тут предсказать что-либо сложно. Это всё я изложил, чтобы объяснить процедурный механизм Turbo Pascal. Хотя такой механизм применяется и в других языках, которых уже не счесть.
 

Процедурные типы

 
Очень важным и удобным расширением языка Turbo Pascal является возможность описания процедурного типа. Процедурный тип – это ни что иное, как обыкновенный указатель, который предназначен для хранения адреса подпрограммы, а не адреса области некоторых данных. Т.е. такая переменная будет занимать 4 байта, и содержать адрес в виде смещения и сегмента, указывающий на начало кода подпрограммы. Можно описать переменную процедурного типа и присвоить ей адрес некоторой процедуры и затем, пользуясь идентификатором этой переменной, вызывать подпрограмму, адрес которой она содержит. Например:
 
type TMyFunc = function ( A, B : real ) : real;
        TMyFunc2 = function ( C, D : real ) : real;
var MyProc : procedure;
       MyFunc, MyFunc2 : TMyFunc;
       MyFunc3 : TMyFunc2;
       P : pointer;
 
procedure MyHalt; far;
begin
Halt;
end;
 
function Imul( M1, M2 : real ) : real; far;
begin
Imul := M1*M2;
end;
 
 
 
 
 
function Idiv( D1, D2 : real ) : real; far;
begin
Idiv := D1/D2;
end;
 
begin
MyProc := MyHalt;
MyFunc := Idiv;
MyFunc2 := MyFunc;
MyFunc3 := MyFunc;
P := @Imul;
TMyFunc(p) := Imul;
writeln(TMyFunc(P)(10,20));
writeln(MyFunc(10,20));
writeln(MyFunc2(10,20));
writeln(MyFunc3(10,20));
end.
 
В данном примере объявлен тип TMyFunc и переменная MyFunc этого типа, которой можно присвоить адрес функции с соответствующим интерфейсом, указанным в описании типа TMyFunc, т.е. функция должна иметь два входных параметра с типом Real и возвращать результат типа Real. Имена входных параметров могут быть произвольными и могут не совпадать по имени с указанными в описании типа. Этому условию соответствуют функции Imul и Idiv. Таким образом, конструкция
 
MyFunc:=Idiv; интерпретируется компилятором не как присвоение результата функции Idiv переменной MyFunc, а как присвоение адреса функции Idiv указателю MyFunc. Только после такой инициализации процедурного указателя MyFunc, его можно использовать, например, в конструкции writeln(MyFunc(10,20));, что будет эквивалентно конструкции writeln(Idiv(10,20));. Аналогично можно поступать и с функцией Imul. При этом, адрес подпрограммы можно передавать из одной переменной процедурного типа в другую MyFunc2:=MyFunc; и после этого применять конструкцию writeln(MyFunc2(10,20));. Корректной будет также конструкция MyFunc3:=MyFunc;, так как по количеству и типу входных параметров, а также типу возвращаемого результата процедурные типы TMyFunc и TMyFunc2, от которых образованы переменные MyFunc3 и MyFunc ничем не отличаются, и значит, являются совместимыми по присваиванию.
 
На примере простого (нетипизированного) указателя P хочу продемонстрировать приведение типа указателя к процедурному типу и таким образом объяснить смысл процедурных типов. В конструкции P:=@Imul; происходит определение адреса функции Imul посредством операции взятия адреса @ и занесение его в нетипизированный указатель P. Такая конструкция вполне корректна, как если бы она выглядела так: TMyFunc(P):=Imul;
 
Но так как указатель P бестиповый, то его нельзя применить как процедурный указатель writeln(MyFunc(10,20));, но можно использовать так writeln(TMyFunc(P)(10,20)); что соответствует правилам приведения типа. (см. “Преобразование типов. Совместимость типов. Явные преобразования” ). Допустимы и конструкции MyFunc:=TMyFunc(P); TMyFunc(P):=MyFunc; и @MyFunc:=P;
 
Аналогично может быть объявлена переменная указателя на процедуру без параметров MyProc и ей может быть присвоен адрес процедуры MyHalt при помощи конструкции MyProc:=MyHalt;.
 
Для того, чтобы подпрограмма могла использоваться в конструкциях присвоения переменной процедурного типа, необходимо, чтобы такая подпрограмма была оформлена с дальним вызовом при помощи служебного слова Far или была заключена в конструкцию директив {$F+}…{$F–}. Иначе компилятор выдаст сообщение “Error 143: Invalid procedure or function reference.” (неправильная ссылка на подпрограмму).
 
Процедурные типы, как и любые другие типы, могут применяться в составлении записей или массивов. Вот листинг в дополнение к предыдущему примеру:
 
type TMyRec = record
          FuncName : string;
          Func : TMyFunc;
         end;
 
var MyRec : TMyRec;
       Funcs : array [0..5] of TMyFunc;
 
begin
MyRec.Func := Imul;
writeln(MyRec.Func(10,20));
Funcs[0] := Idiv;
writeln(Funcs[0](10,20));
end.
 
Для корректной работы с процедурными типами следует соблюдать правила. Подпрограмма, адрес которой присваивается процедурной переменной:
1. должна быть объявлена с дальним вызовом;
2. не может быть стандартной процедурой или функцией. Такое ограничение можно легко преодолеть, описав некоторую подпрограмму, которая будет вызывать необходимую стандартную процедуру или функцию. В качестве наглядного примера сгодится процедура MyHalt, приведённая выше;
3. должна быть описана в глобальном блоке программы, т.е. такая подпрограмма не может быть описана в локальном блоке другой подпрограммы;
4. не может быть описана как обработчик прерывания при помощи служебного слова interrupt и не должна быть inline-подпрограммой.
 
В дополнение к сказанному выше хочу добавить, что подпрограммы могут иметь входные параметры процедурного типа, для того, чтобы быть универсальными и делать различные операции. Пример в дополнение к предыдущему:
 
const MaxElem = 100;
type TArr = array [0..MaxElem] of real;
 
var B : TArr;
       j : word;
  
function Min( M1, M2 : real ) : real; far;
begin
if M1>M2 then Min := M2
   else Min := M1;
end;
 
function Max( M1, M2 : real ) : real; far;
begin
if M1>M2 then Max := M1
   else Max := M2;
end;
 
function Action(var A:TArr; Func:TMyFunc):real;
var j : word;
begin
for j:=0 to MaxElem-1 do Action:=Func(A[j],A[j+1]);
end;
 
begin
for j := 0 to MaxElem do B[j] := random(100);
writeln('Min= ',Action(B,Min));
writeln('Max= ',Action(B,Max));
end.
 
Таким образом, компилятор интерпретирует идентификатор процедурной переменной, указанной в операторе или выражении, как вызов подпрограммы, адрес которой хранится в этой переменной. Если в левой части оператора присваивания стоит имя процедурной переменной, то в правой части должно стоять имя подпрограммы или имя другой процедурной переменной.
 
Другое дело, когда условный оператор имеет вид:
 
if MyFunc(10,20)=Idiv(10,20) then writeln('Равны');
 
то имя процедурной переменной интерпретируется как вызов подпрограммы (функции) и полученное значение сравнивается с результатом вызова функции Idiv. Если есть необходимость сравнить адрес, содержащийся в процедурном указателе MyFunc, с адресом функции Idiv, можно использовать следующий условный оператор:
 
if @MyFunc = @Idiv then writeln('Равны');
 
т.е. оператор @MyFunc преобразует MyFunc в нетипизированный указатель, содержащий адрес, а оператор @Idiv возвращает адрес подпрограммы.
 
Если необходимо получить адрес самой переменной процедурного типа, то следует учесть, что само имя процедурной переменной уже интерпретируется как имя подпрограммы, и значит конструкция @MyFunc возвратит адрес подпрограммы, на которую указывает процедурная переменная MyFunc, а конструкция двойного взятия адреса @@MyFunc возвратит адрес самой переменной MyFunc.
 
Важно помнить, что функция не может возвращать результат процедурного типа, но запросто может возвращать обычный указатель, который затем не сложно привести к процедурному типу.
 
Продолжение следует…
 
© Владислав Демьянишин
 
 
Вы находитесь на официальном сайте Владислава Демьянишина - разработчика игры Dune IV (Dune 4). На нашем сайте Вы можете бесплатно скачать игры Dune IV (Dune 4), Battle City (Танчики с Dendy/Nintendo), читы к играм и многое другое. Также Вы можете скачать бесплатно программы и полезные утилиты. Все программы чистые, т.е. не содержат вирусов и иного вредоносного ПО.
 
Среди доступных программ есть мобильная читалка книг, менеджер переноса файлов с фото- и видеокамер на компьютер, текстовый редактор, WYSIWYG редактор, 3D аниматор, GIF аниматор, AVI аниматор, пакетный конвертор изображений, редактор электрических схем, программа для скриншотов, диспетчер тем рабочего стола и другие.
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 19): Распределение памяти для локальных переменных
 
 
 
15
 
ВКонтакте
Facebook
 
 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
Украинский портАл Украина онлайн Рейтинг@Mail.ru Рейтинг Сайтов YandeG Rambler's Top100