Методы работы со структурой




Структуры

Структура — это тип данных, состоящий из фиксированного числа элементов разного типа.

Для использования структур в программе необходимо выполнить три действия:

1. Задать шаблон структуры, т.е. определить новый тип данных, который впоследствии можно использовать для определения переменных этого типа.

2. Определить экземпляр структуры. Этот этап подразумевает инициализацию конкретной переменной с заранее определенной (с помощью шаблона) структурой.

3. Организовать обращение к элементам структуры.

Разница между описанием структуры в программе и ее определением: описать структуру в программе означает лишь указать ее схему или шаблон; память при этом не выделяется. Этот шаблон можно рассматривать лишь как информацию для транслятора о расположении полей и их значении по умолчанию. Определить структуру — значит дать указание транслятору выделить память и присвоить этой области памяти символическое имя. Описать структуру в программе можно только один раз, а определить – любое количество раз.

1.1. Описание шаблона структуры

Формат задания шаблона структуры:

имя_структуры STRUC

<описание полей>

имя_структуры ENDS

Здесь <описание полей> представляет собой последовательность директив описания данпых db, dw, dd, dq и dt. Их операнды определяют размер полей и при необходимости начальные значения.

Шаблон необходимо поместить в начале сегмента данных либо перед ним. Переменная типа структуры описывается в сегменте данных.

Пример задания структуры (все поля символьные чтобы избежать проблем преобразования данных при вводе):

worker struc;информация о сотруднике

nam db 30 dup (" ");фамилия, имя, отчество

sex db " ";пол

position db 30 dup (" ");должность

age db 2 dup (" ");возраст

standing db 2 dup (" ");стаж

salary db 4 dup (" ");оклад в рублях

birthdate db 8 dup (" ");дата рождения

worker ends

1.2. Определение данных с типом структуры

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

[имя переменной] имя_структуры <[список значений]>

Здесь:

имя переменной — идентификатор переменной данного структурного типа. Задание имени переменной необязательно. Если его не указать, будет просто выделена область памяти размером в сумму длин всех элементов структуры.

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

victor worker <>

Для примера определим несколько переменных с типом описанной выше структуры:

data segment

sotrl worker <”Гурко Андрей Вячеславович",, 'художник', '33', '15', '1800', '26.01.64'>

sotr2 worker <"Mиxaйлова Наталья Геннадьевна", 'ж', 'программист', '30', '10', '1680', '27.10.58'>

sotr3 worker <"Cтeпанов Юрий Лонгинович",, 'художник',, '38', '20', '1750', '01.01.58'>

sotr4 worker <"Юрова Елена Александровна", 'ж', 'связист', '32', 2',, '09.01.66'>

sotr5 worker <>;здесь все значения по умолчанию

data ends

 

Методы работы со структурой

Для обращения к полю структуры используется специальный оператор — символ «.» (точка) следующим образом:

адресное__выражение.имя_поля_структуры

Здесь:

адреснов_выражение — идентификатор переменной некоторого структурного типа или выражение в скобках в соответствии с указанными ниже синтаксическими правилами (рис. 12.1);

имя_поля_структуры — имя поля из шаблона структуры. Это на самом деле тоже адрес, а точнее, смещение поля от начала структуры. Таким образом оператор. вычисляет выражение:

(адресное_выражение) + (имя_поля_структуры)

Пусть требуется извлечь в ах значения поля с возрастом. Так как вряд ли возраст трудоспособного человека будет больше величины 99 лет, то после помещения содержимого этого символьного поля в регистр ах его будет удобно преобразовать в двоичное представление командой aad. Будьте внимательны, так как из-за принципа хранения данных «младший байт по младшему адресу» старшая цифра возраста будет помещена в аl, а младшая – в ah. Для корректировки достаточно использовать команду xchg al,ah;

mov ax,word ptr storl.age;в al возраст storl

xchg ah.al

;a можно и так:

lea bx, storl

mov ax,word ptr [bx].age

xchg ah,al

Определение массива из 10 структур типа worker:

mas_str worker 10 dup (<>)

Дальнейшая работа с массивом структур производится так же, как и с одномерным массивом. Здесь возникает вопросы: как определить размер массива и как организовать индексацию элементов массива?

Аналогично другим идентификаторам, определенным в программе, транслятор назначает имени типа структуры и имени переменной с типом структуры атрибут типа. Значением этого атрибута является размер в байтах, занимаемый полями этой структуры. Извлечь это значение можно с помощью оператора type. После того, как стал известен размер экземпляра структуры, организовать индексацию в массиве структур не представляет особой сложности. К примеру:

worker struc

worker ends

mas_str worker 10 dup (<>)

mov bx,type worker;bx=77

lea di,mas_str

;извлечь и вывести на экран пол всех сотрудников:

mov сх,10

cycl:

mov dl,[di].sex

…; вывод на экран содержимого поля sex структуры worker

add di,bx;к следующей структуре в массиве mas_srt

loop cycl

Копирование поля из одной структуры в соответствующее поле другой структуры. Копирование поля nam третьего сотрудника в поле nam пятого сотрудника:

worker struc

worker ends

mas_str worker 10 dup (<>)

 

mov bx, offset mas_str

mov si,(type worker)*2;si=77*2

add si,bx

mov di,(type worker)*4;s1=77*4

add di,bx

mov cx, 30

rep movsb

На прилагаемой к книге дискете в каталоге.. \lessnl2\struct\ приведена програм­ма, которая осуществляет работу с базой данных о сотрудниках. На ее примере вы можете глубже познакомиться с тем, как организовать работу со структурами в своей программе. Возможно, для читателя имеет смысл и полном объеме иссле-. довать работу этой программы после знакомства с макрокомандами на следую­щем уроке.

 

Объединения

Представим ситуацию, когда мы используем некоторую область памяти для размещения некоторого объекта программы (переменной, массива или структуры). Вдруг после некоторого этапа работы у нас отпала надобность в использовании этих данных. Обычно память остается занятой до конца работы программы. Конечно, в принципе, ее можно было бы использовать для хранения других переменных, по при этом без принятия специальных мер нельзя изменить тип и имя. Неплохо было бы иметь возможность переопределить эту область памяти для объекта с другими типом и именем. Язык ассемблера предоставляет такую возможность в виде специального типа данных, называемого объединением.

Объединение — тип данных, позволяющий трактовать одну и ту же область памяти как имеющую разные типы и имена.

Описание объединений в программе напоминает описание структур, то есть сначала описывается шаблон, в котором с помощью директив описания данных перечисляются имена и типы полей:

имя_объедннения UNION

<описание полей>

имя_объединения ENDS

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

Листинг 12.6, который мы сейчас рассмотрим, примечателен тем, что, кроме демонстрации использования собственно типа данных «объединение», в нем показывается возможность взаимного вложения структур и объединений. Основная идея здесь в том, что указатель на память, формируемый программой, может быть представлен в виде:

– 16-битного смещения;

– 32-битного смещения;

– пары из 16-битного смещения и 16-битной сегментной составляющей адреса;

– пары из 32-битного смещения и 16-битного селектора.

Какие из этих указателей можно применять в конкретной ситуации, зависит от режима адресации (use16 или use32) и режима работы микропроцессора. Так вот, описанный в листинге 12.6 шаблон объединения позволяет нам облегчить формирование и использование указателей различных типов.

Пример использования объединения

;prg12_6.asm

masm

model small

stack 256

.586P

pnt struc;структура pnt, содержащая вложенное объединение

union;описание вложенного в структуру объединения

offs_16 dw?

offs_32 dd?

ends;конец описания объединения

segm dw?

ends;конец описания структуры

.data

point union;определение объединения,

;содержащего вложенную структуру

off_16 dw?

off_32 dd?

point_16 pnt <>

point_32 pnt <>

point ends

tst db "Строка для тестирования"

adr_data point <>;определение экземпляра объединения

.code

main:

mov ax,@data

mov ds,ax

mov ax,seg tst

;записать адрес сегмента строки tst в поле структуры adr_data

mov adr_data.point_16.segm,ax

;когда понадобится можно извлечь значение из этого поля обратно, к примеру, в регистр bx:

mov bx,adr_data.point_16.segm

;формируем смещение в поле структуры adr_data

mov ax,offset tst;смещение строки в ax

mov adr_data.point_16.offs_16,ax

;аналогично, когда понадобится, можно извлечь значение из этого поля:

mov bx,adr_data.point_16.offs_16

exit:

mov ax,4c00h

int 21h

end main

 

Записи

Запись — структурный тип данных, состоящий из фиксированного числа элементов длиной от одного до нескольких бит. При описании записи для каждого элемента указывается его длина в битах и, что необязательно, некоторое значение. Суммарный размер записи определяется суммой размеров ее полей и не может быть более 8, 16 или 32 бит. Если суммарный размер записи меньше указанных значений, то все ноля записи «прижимаются» к младшим разрядам.

Использование записей в программе, так же, как и структур, организуется в три этапа:

1. Задание шаблона записи, то есть определение набора битовых полей, их длин и, при необходимости, инициализация полей.

2. Определение экземпляра записи. Так же как и для структур, этот этап подразумевает инициализацию конкретной переменной типом заранее определенной с помощью шаблона записи.

3. Организация обращения к элементам записи.

3.1. Описание записи

Описание шаблона записи имеет следующий синтаксис:

имя_записи RECORD <описание элементов>

Здесь:

<описание элементов> представляет собой последовательность описаний отдельных элементов записи согласно синтаксической диаграмме (рис. 12.2).

 

 

При описании шаблона память не выделяется, так как это всего лишь информа­ция для транслятора ассемблера о структуре записи. Так же как и для структур, местоположение шаблона в программе может быть любым, но при этом необхо­димо учитывать логику работы однопроходного транслятора.

Синтаксис описания шаблона записи

Имя_ записи record имя_поля:размер[=значение] [,имя_поля:размер[=значение]]

3.2. Определение экземпляра записи

Для использования шаблона записи в программе необходимо определить переменную с типом данной записи, для чего применяется следующая синтаксическая конструкция (рис. 12.3).

Рассмотрим несколько вариантов инициализации.

Рис. 12.3. Синтаксис описания экземпляра записи

 

Если инициализировать поля не требуется, то достаточно указать? при определении экземпляра записи:

iotest record i1:1, i2:2=11, i3:1, i4:2=11, i5:2=00

flag iotest?

При таком описании все ноля переменной типа запись flag обнуляются, несмотря на то, что в определении записи заданы начальные значения полей.

Если требуется частичная инициализация элементов, то они заключаются в угловые (< и >) или фигурные ({ и }) скобки. Различие здесь в том, что в угловых скобках элементы должны быть заданы в том же порядке, что и в определении записи. Если значение некоторого элемента совпадает с начальным, то его можно не указывать, но обязательно обозначить его запятой. Для последних элементов идущие подряд запятые можно опустить.

К примеру, согласиться со значениями по умолчанию можно так:

iotest record i1:1, i2:2=11, i3:1, i4:2=11, i5:2=00

flag iotest <>;согласились со значением по умолчанию

Изменить значение поля i2 можно так;

iotest record i1:1, i2:2=11, i3:1, i4:2=11, i5:2=00

flag iotest <, 10,>; переопределили i2

Применяя фигурные скобки, также можно указать выборочную инициализацию полей, но при этом необязательно обозначать запятыми ноля, со значениями по умолчанию которых мы согласны:

iotest record i1:1, i2:2=11, i3:1, i4:2=11, i5:2=00

flag iotest {i2=10} переопределили i2, не обращая внимания на порядок следования других компонентов записи

 

3.3. Работа с записями

Обычные механизмы адресации здесь бессильны, так как они работают на уровне ячеек памяти, то есть байтов, а не отдельных битов. Здесь программисту нужно приложить некоторые усилия. Прежде всего для понимания проблемы нужно усвоить несколько моментов:

1. Каждому имени элемента записи ассемблер присваивает числовое значение, равное количеству сдвигов вправо, которые нужно произвести для того, чтобы этот элемент оказался «прижатым» к началу ячейки. Это дает нам возможность локализовать его и работать с ним. Но для этого нужно знать длину элемента в битах.

2. Сдвиг вправо производится с помощью команды сдвига shr.

3. Ассемблер содержит оператор width, который позволяет узнать размер элемента записи в битах или полностью размер записи. Варианты применения оператора width:

width имя_элемента_записи — значением оператора будет размер элемента в битах;

width имя_экземпляра_записи

или

width имя типа_записи — значением оператора будет размер всей записи в битах.

mov al,width i2

mov ax,width iotest

4. Ассемблер содержит оператор mask, который позволяет локализовать биты нужного элемента записи. Эта локализация производится путем создания маски, размер которой совпадает с размером записи. В этой маске обнулены биты во всех позициях, за исключением тех, которые занимает элемент в записи.

5. Сами действия по преобразованию элементов записи производятся с помощью логических команд.

Таким образом, непосредственно обратиться к элементу записи невозможно. Чтобы произвести обработку интересующего нас элемента, нужно сначала выделить его, сдвинуть при необходимости к младшим разрядам, выполнить требуемые действия и поместить обратно на свое место в записи. Поэтому, чтобы вам не изобретать каждый раз велосипед, далее мы опишем типовые алгоритмы осуществления этих операций над элементами записи. Ваша задача — закодировать эти алгоритмы тем или иным способом в соответствии с требованиями задачи.

Для выделения элемента записи:

1. Поместить запись во временную память — регистр (8-, 16- или 32-битный, в зависимости от размера записи).

2. Получить битовую маску, соответствующую элементу записи, с помощью оператора mask.

3. Локализовать биты в регистре с помощью маски и команды and.

4. Сдвинуть биты элемента к младшим разрядам регистра командой shr. Число разрядов для сдвига получить с использованием имени элемента записи. В результате этих действий элемент записи будет локализован в начале рабочего регистра и далее с ним можно производить любые действия (как, см. ниже).

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

Чтобы поместить измененный элемент на его место в запись:

1. Используя имя элемента записи в качестве счетчика сдвигов, сдвинуть влево биты элемента записи.

2. Если вы не уверены в том, что разрядность результата преобразований не превысила исходную, можно выполнить «обрезание» лишних битов, используя команду and и маску элемента.

3. Подготовить исходную запись к вставке измененного элемента путем обнуления битов в записи на месте этого элемента. Это можно сделать путем наложения командой and инвертированной маски элемента записи на исходную запись.

4. С помощью команды оr наложить значение в регистре на исходную запись.

В качестве примера рассмотрим листинг 12.7, который обнуляет поле i2 в записи iotest.

;prg_12_7.asm

masm

model small

stack 256

iotest record i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00

.data

flag iotest <>

.code

main:

mov ax,@data

mov ds,ax

mov al,mask i2

shr al,i2;биты i2 в начале ax

and al,0fch;обнулили i2

;помещаем i2 на место

shl al,i2

mov bl,[flag]

xor bl,mask i2;сбросили i2

or bl,al;наложили

exit:

mov ax,4c00h;стандартный выход

int 21h

end main;конец программы

Обратите внимание на то обстоятельство, что мы нигде явно не просчитываем расположение битов. Поэтому, если понадобится изменить размер элемента или его начальное значение, достаточно внести изменения в экземпляр записи или в описание ее типа; функциональную часть программы, работающую с этой записью, трогать не нужно.

3.4. Дополнительные возможности обработки

Понимая важность для эффективного программирования такого типа данных, как запись, разработчики транслятора TASM, начиная с версии 3.0, включили в систему его команд две дополнительные команды на правах директив. Последнее означает, что эти команды внешне имеют формат обычных команд ассемблера, но после трансляции они приводятся к одной или нескольким машинным командам. Введение этих команд в язык TASM повышает наглядность работы с записями, оптимизирует код и уменьшает размер программы. Эти команды позволяют скрыть от программиста действия по выделению и установке отдельных полей записи (мы их обсуждали выше).

Для установки значения некоторого поля записи используется команда setfield с синтаксисом

setfield имя_элемента_записи назначение, регистр_источник

Для выборки значения некоторого поля записи используется команда getfield с синтаксисом

getfield имя_элемента_записи регистр_назначение, источник

Работа команды setfield заключается в следующем. Местоположение записи определяется операндом назначение, который может представлять собой имя регистра или адрес памяти. Операнд имя_элеменга_залиси определяет элемент записи, с которым ведется работа (по сути, он определяет смещение элемента в записи относительно младшего разряда). Новое значение, в которое необходимо установить указанный элемент записи, должно содержаться в операнде регистр_источник. Обрабатывая данную команду, транслятор генерирует последовательность команд, которые выполняют следующие действия:

1. сдвиг содержимого регистр_источник влево на количество разрядов, соответствующее расположению элемента в записи;

2. логическую операцию оr над операндами назначением регистр_источник. Результат операции помещается в операнд назначение.

Важно отметить, что setfleld не производит предварительной очистки элемента, в результате после логического сложения командой оr возможно наложение старого содержимого элемента и нового устанавливаемого значения. Поэтому требуется предварительно подготовить поле в записи путем его обнуления. Действие команды getfield обратно setfleld. В качестве операнда источник может быть указан либо регистр, либо адрес памяти. В регистр, указанный операндом регистр__назначение, помещается результат работы команды — значение элемента записи. Интересная особенность связана с регистр_назначение. Команда getfield всегда использует 16-битный регистр, даже если вы укажете в этой команде имя 8-битного регистра.

Работа с полями записи;prg_12_8.asm

;prg_12_8.asm

masm

model small

stack 256

iotest record i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00

.data

flag iotest <>

.code

main:

mov ax,@data

mov ds,ax

mov al,flag

mov bl,3

setfield i5 al,bl

xor bl,bl

getfield i5 bl,al

mov bl,1

setfield i4 al,bl

setfield i5 al,bl

exit:

mov ax,4c00h;стандартный выход

int 21h

end main;конец программы

В листинге 12.8 демонстрируется порядок извлечения и установки некоторых полей записи. Результат работы команд setfield и getfield удобнее всего изучать в отладчике. При установке значений полей не производится их предварительная очистка. Это сделано специально. Для такого рода операций лучше использовать некоторые универсальные механизмы, иначе велик риск внесения ошибок, которые трудно обнаружить и исправить. В качестве такого механизма можно предложить макрокоманды, к рассмотрению которых мы и приступим на следующем уроке.

В заключение хотелось бы привести еще один пример использования записей. Это описание регистра eflags. Для удобства это описание мы разбили па три части: eflags_l_7 младший байт eflags/flags, eflags_8_15 — второй байт eflags/flags, eflags_h — старшая половина eflags.

eflags_1_7 record sf7:l=0, zf6:l=0, c5:l=0, af4:l=0, c3:l=0, pf2:l=0, c1:=l.cf0:l=0

eflags_l_15 record cl5:l=0, ntl4:l=0, iopl:2=0, ofll:l=0, df10:l=0, if9:1=1, tf8:l=0

ef1ags_h record с:13=0, ac18:1=0, vm17:1=0, rf16:1=0

Запомните это описание. Когда вы освоите работу с макрокомандами и столкнетесь с необходимостью задействовать регистр флагов, то вы сразу же захотите написать соответствующую макрокоманду. Эта макрокоманда, избавит вас от многих трудно обнаруживаемых ошибок.



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-04-27 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: