Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.97.14.86] |
|
Сообщ.
#1
,
|
|
|
В любом паскале есть набор различных типов переменных - будь то число, строка, символ, или булева переменная, даже массив (array), или запись (record) это сложные типы, состоящие из переменных. Когда вы объявляете какую либо переменную после слова var, то вы указываете ее имя и тип.
Например var c:char; {переменная - один символ с именем "с"} s:string {переменная - строка из 255 символов с именем "s"} a:array[1..100] of byte; {структура - массив - набор переменных (чисел типа byte), объединенных под одним именем "a"} Это все всем известно, и давно понятно. Но что же делается внутри паскаля, когда мы так объявляем переменные? Каждая откомпилированная программа при запуске состоит из сегмента кода, и сегмента данных - это две области информации. В сегменте кода находятся все наши команды (преобразованные в машинный код). А в сегменте данных находятся все наши данные - это попросту говоря и есть переменные. Так вот командой var c:byte мы заставляем компилятор откомпилировать наш код так, чтобы при запуске программы в сегменте данных выделилась ячейка памяти, способная вместить в себя один байт. И называем эту ячейку именем "с". Из этого можно сделать вывод: каждая переменная, которую мы объявили в программе имеет свою ячейку (или ячейки) в памяти, а точнее в сегменте данных. Расстояние от начала сегмента данных до этой ячейки называется относительным адресом, или в данном случае просто Адрес ячейки. (Относительный адрес - адрес относительно какого либо сегмента. В данном случае у нас всегда относительно сегмента данных. Поэтому слово "относительный" я не пишу) Во время компиляции паскаль генерирует программу таким образом, чтобы когда она запустилась в памяти выделились все ячейки под все переменные. Сам же компилятор в код программы во всех вызовах переменных подставляет предполагаемый адрес ячейки. Вот более понятный пример такой схемы: Мы написали программу: var a:word; s:string; begin s:='Число: '; a:=10; writeln(s,a); end. И нажали ctrl+F9 (откомпилировали). что делает компилятор? Он читает программу. Видит переменные - "а" и "s". И компилирует программу с такими командами в начале: *Выделить в программе два байта в сегменте данных под переменную "a". И сам себе запоминает, что адрес этой переменной будет 0 (ноль) от начала сегмента данных. *Выделить в программе ячейки в сегменте данных под переменную "s". Выделит он 256 байт (255 под предполагаемую строку и 1 под байт - размер строки). А для себя компилятор запомнит, что адрес переменной "s" будет 2 от начала сегмента данных. 2 потому что нулевой байт и 1 байт сегмента данных - это два байта переменной "a". вот начало сегмента данных: || - участок переменной "а" с адресом 0 aasssssssssssss.........ssss <-- а и s это участки, не содержимое ячеек | .....| - участок переменной "s" с адресом 2 После этого компилятор будет компилировать следующие строки. Строка "s:='Число: ';" будет пОнята и преобразована компилятором так: Занести в ячейки начиная со второй (2 - адрес переменной "s") сначала один байт - количество символов строки 'Число: ', и ещё 7 байт - сами символы. Количество символов при этом сразу же посчитается, потому что эта строка - константа, и сами символы занесутся в память примерно так: * в ячейку с адресом 2 (этот адрес компилятор запомнил, когда читал объявление переменной "s") занести число 7 (количество символов строки) * в ячейку с адресом 2+1> занести букву 'Ч' * в ячейку с адресом 2+2> занести букву 'и' и так далее. Затем выполнится строка "a:=10;". Компилятор преобразует это в такую команду: Занести в две ячейки с адресом 0 (этот адрес компилятор запомнил, когда читал объявление переменной "а") число 10 в виде машинного слова. (Машинное слово - 2 байта. Число 10 будет представлено в виде двух байт, которые компьютер распознает как десятичное число 10) И наконец выполнится строка "writeln(s,a)", которую компилятор преобразует в такие команды: * нарисовать на экране побайтно содержимое ячеек памяти начиная с третей (адрес первой буквы переменной "s") и рисовать эти буквы пока не нарисуется столько, сколько находится в ячейке "2" (адрес переменной "s", ведь первый ее байт - количество букв в строке) * преобразовать содержимое нулевой и первой ячеек памяти, воспринимая их как машинное слово в строку с десятичным числом, и побайтно вывести эту строку на экран. В этом втором пункте реализуется преобразование из числа в строку. Такое преобразование требует новой переменной - строки, для которой также формируются команды, которые будут выделять память под строку, но такое формирование происходит при разборе содержимого процедуры writeln. Еще один немаловажный аспект: Переменные внутри подпрограмм (процедур, функций) При написании подпрограмм нам часто приходится использовать локальные переменные - те, которые находятся и существуют только внутри процедур или функций, и не существуют за их пределами. Эти переменные имеют тот же принцип хранения в памяти, что и обычные, точно так же генерируются коды, которые выделяют под них память, и точно так же каждой переменной сопоставляется адрес, который используется взамен всех имен этих переменных. Но Данные (ячейки для переменных) выделяются не в сегменте данных, а в сегменте стека. Это дополнительный сегмент, специально созданный для хранения временных переменных. При объявлении подпрограммных переменных компилятор создает код, который выделяет в стеке память под наши переменные, сопоставляет на участке подпрограммы этим переменным их адреса (расстояния от начала стека), и использует их практически так же как и обычные переменные. когда компилятор встречает слово "end", означающее конец процедуры или функции, то он автоматически генерирует код, который освобождает стек от зарезервированных под переменные ячеек. То есть как бы говорит программе: Мне эти ячейки уже не нужны. можно их использовать для других переменных в дальнейшем. Как передаются параметры к функциям и процедурам? Когда какая нибудь подпрограмма, будь то процедура или функция объявлены к примеру в таком виде: procedure MyProc(Myparam1,Myparam2:Type1;Myparam3:Type2) У этой подпрограммы есть параметры. Их три - Myparam1, Myparam2 одного типа, и Myparam3 другого типа. Когда мы вызываем эту процедуру, то в качестве этих параметров мы чаще всего указываем реальные глобальные переменные, то есть те, которые у нас уже существуют в программе. Как же эти переменные понимаются внутри подпрограммы ? Все просто: Ведь мы знаем, что вызывая эту процедуру пользователь в качестве параметров укажет реальные глобальные переменные, или даже константу (например цифру один, или строку 'abc'). Поэтому когда компилятор в программе встречает строку с вызовом нашей процедуры, то генерирует такой код: поместить значение первого параметра, указанного в найденном месте в стек, потом тоже сделать для второго и для третьего. Допустим мы вызываем процедуру так: MyProc(a1,a2,a3). Тогда значения переменных a1, a2 и a3 поместятся в стек, и естественно компилятор запоминает относительный адрес этих переменных с сегменте стека, а так же запоминает для себя какой адрес как называется внутри подпрограммы(Myparam1, Myparam2 и т.д.). Эти имена потом будут учитываться при разборе текста подпрограммы. После этого уже генерируется код, в котором указан переход к выполнению подпрограммы. И таким образом глобальные переменные, или даже константы попадают в стек, и поэтому внутри подпрограммы мы можем изменять их значения, не боясь, что изменятся и значения глобальных переменных, которые мы передали в качестве параметров при вызове процедуры. Вот пример того, что переменные попадают в стек, и не используются глобальные: var gl1,gl2:byte; procedure myproc(a1,a2:byte); begin a1:=20; a2:=10; end; begin gl1:=10; gl2:=20; writeln('gl1=',gl1,' gl2=',gl2); myproc(gl1,gl2); writeln('gl1=',gl1,' gl2=',gl2); end. По окончании выполнения подпрограммы компилятор сгенерирует код возврата туда, откуда она была вызвана, и после этого еще код, в котором будет освобождена память из-под трех переменных в стеке. Теперь о том, как передаются параметры, если их объявили вот так: procedure MyProc(var Myparam1,Myparam2:Type1;var Myparam3:Type2) Тут даже проще. В таком случае словом var мы говорим компилятору - эти переменные - глобальные переменные. Не нужно для них выделять память в стеке, пусть они останутся глобальными. Очевидно, что когда паскаль найдёт в программе такой вызов:MyProc(a1, a2, a3), то он не станет генерировать код, в котором будет помещать значения переменных a1,a2 и a3 в стэк. Он просто напросто запомнит, что эти переменные теперь в подпрограмме имеют имена Myparam1, Myparam2 и Myparam3. А в теле подпрограммы, когда найдено обращение к ним, то будет использоваться адрес глобальной переменной. Адрес будет уже не относительным (читайте в самом начале топика про относительные адреса) а полным. То есть запись будет производится в <сегмент данных>:<адрес переменной>. Если бы он был относительным, то мы бы использовали адрес переменной относительно сегмента данных, но брали бы переменную с таким адресом, но относительно сегмента стека. И получили бы полную чепуху. В целом - если параметр переменной передан как var, то на самом деле это не параметр, а глобальная переменная с другим именем. И ее изменение неизбежно изменит значение переменной, переданной в качестве параметра (ведь они - одно и то же). Но от пользователя этот механизм скрыт. Вот пример и доказательство: var a1:byte; procedure MyProc(var n:byte); begin n:=300; end; begin a1:=100; writeln('a1=',a1); MyProc(a1); writeln('a1=',a1); end. И наконец как передаются параметры, если их объявили вот так: procedure MyProc(const Myparam1,Myparam2:Type1) Тут все еще проще - в данном случае используется такой же метод, как и при директиве var, с одной лишь разницей - внутри подпрограммы нельзя изменить значение переменной, переданной в качестве параметра. Если к примеру написать внутри подпрограммы MyParam1:=10; То паскаль при компиляции остановится с ошибкой - "Error 122: Invalid variable reference." - неправильное обращение к переменной. На самом деле это всего лишь значит, что значения параметров, объявленных как const нельзя поменять внутри подпрограммы. Чтобы полностью исчерпать тему дам совет - если в качестве параметров процедуры вы передаете переменные большого объема, например массивы, то весь массив помещается в стек. На это уходит куча времени, и куча памяти стека. Запросто может возникнуть переполнение. Поэтому массивы я бы советовал передавать с директивой const, в случае, когда внутри подпрограммы содержимое массива меняться не будет, а будет только лишь читаться (наиболее часто возникающая ситуация). Или с директивой var, в случае, когда вам неважно, что будет находиться в массиве после выполнения процедуры. |