Далее обсуждаются следующие темы:
const
и volatile
Структура — тип, состоящий из группы последовательно расположенных членов различных типов. Подобно другим типам структура только описывает модель хранения и интерпретации данных и не резервирует память. Память отводится, когда декларируется объект как экземпляр такой структуры.
Каждый член структуры, за исключением битовых полей, должен иметь имя.
В Watcom C/16 и C/32 член структуры может не иметь имени, если он — структура или объединение.
Структура не может содержать в себе члены незавершённого типа. В частности, она не может содержать в себе член того же типа, что и определяемая структура (в противном случае структура будет иметь неопределённый размер), однако она может содержать указатель на свой тип.
Структуре можно присвоить необязательный тег, по которому на эту структуру можно будет ссылаться в другом месте программы. Если тег не задать, то данный тип структуры будут иметь только объекты, перечисленные после декларации.
Пространство имени тега структуры не перекрывается с пространством имён объектов, меток и членов структур, поэтому тег может совпадать с ними. Тег структуры не может совпадать с тегами объединений, перечислимых типов или других структур.
Каждая структура имеет своё пространство имени, поэтому идентификатор может быть использован как имя члена более, чем в одной структуре. Идентификатор, который является именем объекта, тегом структуры или объединения, именем члена объединения, тегом перечисления или меткой, можно также использовать в качестве имени члена без какой-либо неоднозначности.
Структуры помогают организовать данные программы, объединяя родственные объекты в один. Они также используются для создания связанных списков и деревьев и для описания внешних областей данных, с которыми должно работать приложение.
Следующая структура может быть использована для описания лексемы командной строки:
struct tokendef { int length; int type; char text[80]; };
Она определяет структуру, состоящую из трёх членов, целого, содержащего длину
лексемы, целого, содержащего некоторый закодированный тип лексемы и массив из
80 символов, содержащих текст лексемы. Тег структуры —
tokendef
.
Предыдущее определение не создаёт объект, содержащий структуру. Чтобы создать
экземпляры структуры, нужно привести список идентификаторов после определения
структуры или при декларировании объекта на месте типа использовать
struct tokendef
. Например,
struct tokendef { int length; int type; char text[80]; } token;
эквивалентно
struct tokendef { int length; int type; char text[80]; }; struct tokendef token;
Оба создают token
как экземпляр структуры tokendef
.
Тип объекта token
— struct tokendef
.
Ссылка на член структуры производится при помощи оператора точка
(.
). Первый операнд оператора .
— объект,
содержащий структуру. Второй операнд — имя члена. Например,
token.length
ссылается на член length
структуры
tokendef
, которая содержится в token
.
Если tokenptr
определён как
struct tokendef * tokenptr;
(tokenptr
— указатель на структуру tokendef
), то
(*tokenptr).length
ссылается на член length
структуры tokendef
, на
которую указывает tokenptr
. Для ссылки на член структуры можно
использовать альтернативный оператор стрелка (->
):
tokenptr->length
эквивалентно
(*tokenptr).length
Если структура содержит неименованные члены — структуру или объединение, то на члены вложенной структуры или объединения ссылаются как на поля внешней. Например,
struct outer { struct inner { int a, b; }; int c; } X;
На члены объекта X
ссылаются следующим образом:
X.a
, X.b
и X.c
.
Каждый член структуры имеет более старший адрес, чем предыдущий. В результате выравнивания членов между ними могут образовываться неименованные промежутки, а также неименованная часть в конце структуры.
У компиляторов Watcom C/16 и C/32 есть ключ командной строки и директива
препроцессора #pragma
для управления выравниванием членов
структуры. Подробности см. «User's Guide».
Кроме того, имеется ключевое слово _Packed
, которое,
будучи указано перед ключевым словом struct
, делает
структуру упакованной (без выравнивания и промежутков) вне зависимости от
ключей командной строки или директивы #pragma
.
Указатель на объект структурного типа, правильно приведённый, является также указателем на первый член структуры.
Декларация структуры в следующем виде
struct тег;
может быть использована для декларации новой структуры внутри блока и временного сокрытия старой структуры. Когда блок закончится, снова будет действовать предыдущая декларация. Например,
struct thing { int a,b; }; /* ... */ { struct thing; struct s1 { struct thing * thingptr; } tptr; struct thing { struct s1 * s1ptr; } sptr; }
Первоначальное определение struct thing
подавляется для создания
нового. Если этого не сделать, то thingptr
будет указателем на
старое определение структуры thing
, а не на новое.
Переопределение структур может создать путаницу и его лучше избегать.
Член структуры может быть декларирован как битовое поле типа
int
, unsigned int
или signed int
.
Кроме того, компиляторы Watcom C/16 и C/32 допускают типы
char
, unsigned char
, short
int
и unsigned short int
в качестве типа
битового поля.
Битовое поле декларирует член числом занимаемых битов. Значение битовому полю можно присваивать также, как и другим целым типам, в диапазоне, который может быть представлен таким количеством битов. Если значение слишком большое, при присвоении лишние старшие биты будут отбрасываться.
Тип битового поля определяет как будет трактоваться его самый старший бит. У
знаковых типов он трактуется как знаковый, а у беззнаковых — нет. Для
битового поля определённого как int
без ключевых слов
signed
или unsigned
то, будет ли считаться старший
бит знаковым, зависит от реализации.
Компиляторы Watcom C/16 и C/32 трактуют старший бит битового поля типа
int
как знаковый.
Битовое поле декларируется при помощи двоеточия идущего за именем члена и константного выражения, результатом которого является неотрицательное значение, не превышающее число битов в типе.
Битовое поле может декларироваться без имени и использоваться для выравнивания структуры по нужной форме. К таким полям нельзя ссылаться.
Если два битовых поля декларированы последовательно внутри одной структуры и оба умещаются внутри одной ячейки памяти, то они хранятся вместе. Если второе не умещается, то будет ли оно располагаться в следующей ячейке или частично — в той же, а остаток в следующей, зависит от реализации.
Компиляторы Watcom C/16 и C/32 помещают битовое поле в следующую ячейку памяти, если оно не умещается в оставшуюся после первого битового поля часть. Битовые поля не могут пересекать границы ячеек.
Неименованный член декларированный как: 0
предотвращает
следующее битовое поле от помещения в ту же ячейку памяти, что и предыдущее.
Расположение битовых полей в памяти зависит от реализации.
Компиляторы Watcom C/16 и C/32 помещают битовые поля начиная младшей (с
самого младшего бита) части ячейки памяти. Если 1-битное битовое поле
помещается одно как unsigned int
, то 1 в битовом поле
соответствует целочисленной единице.
Рассмотрим следующее определение структуры:
struct list_el { struct list_el * link; unsigned short elnum; unsigned int length : 3; signed int offset : 4; int flag : 1; char * text; };
Структура list_el
содержит следующие члены:
link
является указателем на структуру list_el
,
показывая, что, возможно, экземпляры этой структуры будут использоваться в
связанном списке,
elnum
— беззнаковое короткое целое,
length
— беззнаковое битовое поле занимающее 3 бита и
допускающее значения от 0 до 7,
offset
— знаковое битовое поле, занимающее 4 бита,
которое будет помещено в целое вместе с length
. Так как его
тип — signed int
, то его значения будут в диапазоне от
-8 до 7,
flag
— 1-битное поле,
Так как тип — int
, то компиляторы Watcom C/16
и C/32 будут трактовать этот бит как знаковый, и значения битового поля
будут -1 и 0.
text
— указатель на символ, возможно стринг.
Объединение похоже на структуру, с той разницей, что каждый его член располагается начиная с того же места, а не последовательно друг за другом. (В Паскале используется термин «запись с вариантами».)
Пространство имени тега объединения не перекрывается с пространством имён объектов, меток и имён членов, поэтому тег может совпадать с ними. Тег не может совпадать с тегами структур, перечислимых типов или других объединений.
Каждое объединение имеет своё пространство имён, поэтому идентификатор может использоваться в качестве имени члена в нескольких различных объединениях. Идентификатор, который является именем объекта, члена структуры, тегом объединения или перечисления или меткой, может использоваться как имя члена без неоднозначностей.
В Watcom C/16 и C/32 объединения, подобно структурам, могут содержать неименованные члены — структуры или объединения. Ссылка на члены неименованной структуры или объединения производится как если бы члены вложенной структуры или объединения были на внешнем уровне.
Размер объединения — размер наибольшего члена, который оно содержит.
Указатель на объект объединения указывает на каждый член объединения. Если один или более членов объединения — битовые поля, то указатель на объект также указывает на ячейку памяти, где располагается это битовое поле.
Сохранение значения в одном члене объединения и обращение к нему через другой член только прибавляет выразительности, когда разные члены имеют один тип. Членами объединения могут быть структуры, и, если некоторые или все они начинаются с одинаковых членов, то обращение к этим членам структуры может производится через члены объединения. Рассмотрим, например, следующие определения структур и объединения:
struct rec1 { int rectype; int v1,v2,v3; char * text; }; struct rec2 { int rectype; short int flags : 8; enum {red, blue, green} hue; }; union alt_rec { struct rec1 val1; struct rec2 val2; };
alt_rec
— объединение определяющее два члена
val1
и val2
— две разные формы записи, а
именно структуры rec1
и rec2
соответственно. Каждая
запись начинается с члена rectype
. Следующий фрагмент программы
будет допустим:
union alt_rec record; /* ... */ record.rec1.rectype = 33; DoSomething( record.rec2.rectype );
Однако следующий фрагмент демонстрирует реализационно-зависимое поведение:
record.rec1.v1 = 27; DoSomethingElse( record.rec2.hue );
Другими словами, за исключением случая, когда членами объединения являются структуры, чьи первые члены имеют одинаковый тип, программа не должна сохранять значение в одном члене объединения и извлекать его из другого. Обычно используется флаг или другой индикатор, чтобы показать какой член объединения «активен» в данный момент.
Указатель на объект эквивалентен его адресу в памяти компьютера.
Объект может быть декларирован как указатель на тип объекта, или же он может быть декларирован как указатель на неконкретный тип. Например,
тип * идентификатор;
декларирует идентификатор как указатель на данный тип. Если
тип — void
, то идентификатор —
указатель на неконкретный тип объекта (обобщённый указатель).
Следующие примеры демонстрируют различные декларации указателей:
int * intptr;
intptr
— указатель на int
.
char * charptr;
charptr
— указатель на char
.
struct tokendef * token;
token
— указатель на структуру tokendef
.
char * argv[];
argv
— массив указателей на char
или массив указателей на стринги.
char ** strptr;
strptr
— указатель на указатель на char
.
void * dumpbeg;
dumpbeg
— указатель, но не на какой-то конкретный тип объекта.
В любом месте, где можно использовать указатель, можно также использовать
константу 0
. Это значение называется константа
"null-указатель". Внутренне используемое значение для null-указателя,
гарантирует, что он не указывает на какой-либо объект. Это необязательно
соответствует целому значению 0. Просто оно представляет указатель, который в
данный момент не указывает на что-либо. Макро NULL
, определённое
в заголовочном файле <stddef.h>
также может использоваться
вместо 0.
Примечание: следующий раздел касается только компилятора Watcom C/16 (16-битного). Для компилятора Watcom C/32 см. раздел Специальные типы указателей Watcom C/32.
На процессорах 8086 обычный указатель (16-битный) может указывать только на 64 килобайта из всей доступной на компьютере памяти. Это накладывает на программу ограничения в виде 64K исполнимого кода и 64K данных. Для многих приложений этого достаточно.
Некоторым приложениям требуется больше, чем 64K кода или данных, или обоих. Компилятор Watcom C/16 предлагает механизм, благодаря которому указатели могут декларироваться так, что могут преодолеть лимит 64K. Это можно сделать либо указав опцию при компиляции файлов (см. «Руководство пользователя»), либо включив в декларацию объекта специальное ключевое слово, квалификатор. Остаток этого раздела описывает эти ключевые слова и их использование.
Использование этих ключевых слов может помешать компиляции с использованием других C-компиляторов, в частности при переносе на другую систему. Однако для их исключения можно использовать препроцессор.
До обсуждения специальных типов указателей необходимо иметь понятие о различных моделях памяти, которые доступны, и что они значат. Имеются пять моделей памяти:
small (малая)
мало кода (< 64K), мало данных (< 64K)
compact (компактная)
мало кода (< 64K), много данных (всего данных > 64K, каждый объект < 64K)
medium (средняя)
много кода (> 64K), мало данных (< 64K)
large (большая)
много кода (> 64K), много данных (всего данных > 64K, каждый объект < 64K)
huge (огромная)
много кода (> 64K), огромные данные (всего данных > 64K, есть объекты > 64K)
В последующих разделах модели памяти обсуждаются в терминах «малая» и «большая» модель кода и данных. Термины «малая», «компактная», «средняя», «большая» и «огромная» — просто сокращённые термины для обозначения доступных комбинаций моделей кода и данных.
Каждая программа может использовать либо малую модель кода (меньше, чем 64K), либо большую модель кода (больше, чем 64K). Малая модель означает, что все функции (вместе) должны занимать меньше 64K. Вызов функции можно осуществлять только с использованием 16-битного указателя. Это по умолчанию.
Большая модель отменяет это ограничение, но требует, чтобы все функции вызывались через 32-битный указатель. 32-битный указатель состоит из двух 16-битных величин, называемых сегмент и смещение. (Когда компьютер использует сегмент и смещение для обращения к конкретному месту в памяти, эти две величины комбинируются для получения 20-битного адреса, что позволяет адресовать до 1024K памяти.) Так как указатели в этом случае больше, то большой код занимает больше места и медленнее работает.
При использовании большой модели кода можно группировать функции в несколько частей по 64K или меньше. Каждый модуль может составлять такую часть, можно также группировать несколько модулей. Становится возможным вызывать функции внутри одной группы при помощи 16-битного значения. Про такие функции говорят, что они ближние (near). Функции вне группы по-прежнему будут вызываться, но с использованием 32-битного значения. Про такие функции говорят — дальние (far).
Если во время компиляции модуля в командной строке указана опция большой модели кода, то простые указатели на функции автоматически будут большого типа, и все вызовы будут производиться с использованием большего (32-битного) способа.
Возможно также использование опции малой модели кода и изменение некоторых функций и указателей на функции на дальние. Однако этот метод может повлечь за собой некоторые проблемы. Компилятор Watcom C/16 генерирует специальные вызовы функций, которые программист не видит, например, для проверки переполнения стека перед вызовом функции. Эти вызовы могут быть либо ближними, либо дальними в зависимости от модели памяти, выбранной во время компиляции. Если используется малая модель кода, все вызовы будут ближними. Если же несколько групп кода созданы с дальними вызовами между ними, им всем будет необходим доступ к подпрограмме проверки переполнения стека. Линкер может поместить эту специальную подпрограмму в одну кодовую группу, оставив другие функции без доступа к ней и, приведя тем самым к ошибке.
Для разрешения этой проблемы, смешивание моделей кода требует, чтобы все модули компилировались в большой модели кода с переопределением нужных функций ближними. В этом случае подпрограмма проверки переполнения стека может быть помещена в любую кодовую группу, к которой можно обращаться из всех остальных. В качестве альтернативы можно использовать ключ командной строки для отключения её вызова.
Каждая программа может использовать либо малую модель данных (меньше, чем 64K), либо большую модель (больше, чем 64K). Малая требуют, чтобы все объекты умещались в 64K памяти. Для обращения к каждому объекту будет использоваться 16-битный указатель. Это по умолчанию.
Большая модель данных отменяет это ограничение, но все указатели на объекты данных становятся 32-битными указателями. Как и с большой моделью кода, в этом случае требуются дополнительные инструкции для работы с 32-битными указателями, поэтому сгенерированный код будет больше и не таким быстрым.
И в малой, и в большой модели каждый объект будет ограничен в размере 64K
байтами. Однако объект может быть декларирован как огромный (huge) и
быть больше 64K байт. Указатели на огромные объекты наименее эффективны, так
как требуют дополнительный код для управления, особенно в адресной
арифметике. Огромные объекты обсуждаются в разделе Ключевое слово
__huge
Watcom C/16.
Когда используется опция большой модели данных, программа по-прежнему сохраняет одну область размером до 64K, в которой на объекты можно ссылаться, используя 16-битные указатели независимо от группы кода, которая исполняется. Эти объекты называются ближними (near). На объекты вне этой области по-прежнему можно ссылаться, но с использованием 32-битных значений. Эти объекты называются дальними (far).
При компиляции модуля с использованием в командной строке опции большой модели данных, обычные указатели на объекты отличные от функций будут определяться автоматически большего типа.
Возможно также использовать опцию малой модели данных и изменять некоторые объекты на дальние. Программист сам должен решать что проще использовать.
Есть возможность смешивать малые и большие модели кода и данных внутри программы. На самом деле, программист, борющийся за оптимальную эффективность, возможно будет смешивать типы указателей. Но это нужно делать с большой осторожностью!
В некоторых приложениях программист может захотеть иметь возможность работать с большими кодом или данными, но не захочет расплачиваться за это дополнительным кодом, компилируя всё одинаково. В случае больших данных он может реализовать 99% структур данных внутри ограничения в 64K, а оставшиеся — вне этого предела. Подобным образом, было бы хорошо иметь только несколько функций, которые не умещаются в 64K.
При переопределении текущей модели памяти очень важно правильно декларировать каждый тип.
Последующие разделы описывают как переопределять текущую модель памяти.
__far
Watcom C/16
Когда используется большая модель кода, функции являются дальними, а
указатели на функции автоматически декларируются как указатели на
дальние функции. Похожим образом большая модель данных делает все
указатели на объекты (не функции) указателями на дальние объекты.
Однако при использовании малых моделей кода или данных при помощи ключевого
слова __far
можно привести объект к большой модели.
Ключевое слово __far
— квалификатор типа, который изменяет
лексему, следующую за ним. Если __far
предшествует
*
(как в __far *
), то указатель указывает на нечто
дальнее. А если __far
предшествует идентификатору объекта или
декларируемой функции (как в __far x
), то уже сам объект —
дальний.
Ключевое слово __far
может применяться только к именам функции
или объекта, или к символу оператора косвенности (указателя) *
.
Параметры функций не могут декларироваться как __far
, так
как они всегда внутри области данных размером в 64K и, следовательно, —
ближние.
Watcom C/16 предоставляет предопределённые макро far
и _far
для удобства и совместимости с компилятором
Microsoft C. Они могут использоваться вместо __far
.
Следующие примеры демонстрируют использование ключевого слова
__far
. Они подразумевают, что используется малая модель памяти
(малые модели кода и данных).
int __far * ptr;
декларирует ptr
как указатель на целое. Объект ptr
— ближний (адресуется с использованием 16 бит), но значение указателя
— адрес дальнего целого и содержит 32 бита.
int * __far fptr;
также декларирует fptr
как указатель на целое. Однако объект
fptr
— дальний, а целое, на которое он указывает, —
ближнее.
int __far * __far ffptr;
декларирует ffptr
как указатель (дальний) на целое (также дальнее).
Если при декларации функции перед её именем поместить ключевое слово
__far
, то компилятор будет считать её дальней. Важно, чтобы до
любого её вызова, который делается до её определения, был приведён
прототип функции. Например, декларация
void __far BubbleSort();
декларирует функцию BubbleSort
как дальнюю, означая, что любой
её вызов должен быть дальним.
Вот ещё несколько примеров. Они также подразумевают использование малой модели памяти (малые модели кода и данных).
struct symbol * __far FSymAlloc( void );
декларирует функцию FSymAlloc
как дальнюю, возвращающую
указатель на ближнюю структуру symbol
.
struct symbol __far * __far FFSymAlloc( void );
декларирует функцию FFSymAlloc
как дальнюю, возвращающую
указатель на дальнюю структуру symbol
.
void Indirect( float __far fn() );
декларирует функцию Indirect
как ближнюю, принимающую один
параметр fn
, который является указателем на дальнюю функцию,
возвращающую float
.
int AdjustLeft( struct symbol * __far symptr );
является неправильной декларацией, так как пытается декларировать
symptr
как дальний. Все параметры должны быть ближними, так как
располагаются в области данных размером 64K, которая адресуется как ближняя.
__near
Watcom C/16
Когда используется малая модель кода, функции являются ближними, а
указатели на функции автоматически декларируются как указатели на
ближние функции. Подобно этому малая модель данных делает все
указатели на объекты (не функции) указателями на ближние объекты.
Однако при использовании большой модели кода или данных, можно использовать
ключевое слово __near
, чтобы привести объект к малой модели.
Ключевое слово __near
— квалификатор типа, который
изменяет лексему, следующую за ним. Если __near
предшествует
*
(как в __near *
), то указатель указывает на нечто
ближнее. А если __near
предшествует идентификатору объекта или
декларируемой функции (как в __near x
), то сам объект —
ближний.
Ключевое слово __near
может применяться только к именам функции
или объекта, или к символу оператора косвенности (указателя) *
.
Watcom C/16 предоставляет предопределённые макро near
и _near
для удобства и совместимости с компилятором
Microsoft C. Они могут использоваться вместо __near
.
Следующие примеры демонстрируют применение ключевого слова
__near
. Эти примеры подразумевают, что используется большая
модель памяти (большие модели кода и данных).
extern int __near * x;
декларирует x
как указатель на ближнее целое. (x
не
обязательно находится в области данных размером 64K, где он — ближний,
а вот целое, на которое он указывает, да.)
extern int * __near nx;
декларирует объект nx
как ближний, указывающий на дальнее целое.
(nx
находится внутри области данных размером 64K, где он
является ближним, а вот целое, на которое он указывает — может быть
вне.)
extern int __near * __near nnx;
декларирует объект nnx
как ближний, указывающий на ближнее
целое. (Оба, nnx
и целое, на которое оно показывает, находятся в
ближней области размером 64K.)
struct symbol * __near NSymAlloc( void );
декларирует функцию NSymAlloc
как ближнюю, возвращающую
указатель на дальнюю структуру symbol
.
struct symbol __near * __near NNSymAlloc( void );
декларирует функцию NNSymAlloc
как ближнюю, возвращающую
указатель на ближнюю структуру symbol
.
__huge
Watcom C/16
Даже при использовании большой модели данных, размер каждого объекта
ограничен 64K. Некоторым приложениям необходимо преодолевать это ограничение.
Компилятор Watcom C/16 предлагает ключевое слово __huge
для
описания объектов больших 64K. Генерируемый код для таких объектов менее
эффективен, чем для объектов __far
.
Декларирование таких объектов производится по такому же образцу, что и
приведённые выше, но с ключевым словом __huge
, предшествующим
имени объекта, если объект больше, чем 64K, или предшествующим
*
, если это указатель на объект больший, чем 64K.
Ключевое слово __huge
может применяться только к массивам.
Огромные объекты могут использоваться и в малой, и в большой моделях памяти.
Watcom C/16 предоставляет предопределённые макро huge
и _huge
для удобства и совместимости с компилятором
Microsoft C. Они могут использоваться вместо __huge
.
Данные примеры демонстрируют использование ключевого слово
__huge
. Они подразумевают, что используется большая модель кода
и малая данных (средняя модель памяти).
int __huge iarray[50000];
декларирует объект iarray
как массив из 50000 целых, с общим
размером в 100000 байт.
int __huge * iptr;
декларирует iptr
как ближний объект, указывающий на целое, часть
огромного массива, такое как элемент массива iarray
.
На процессоре 80386 в «защищённом» режиме обычный указатель (32-битный) может адресовать области доступной на компьютере памяти размером в 4 гигабайта (4.294.967.296 байт). (На практике этот объём может быть меньше.) Эти области памяти называются сегментами, и в памяти их может быть определено более одного. Каждый 32-битный указатель на самом деле — смещение внутри 4-х гигабайтного сегмента, и смещения внутри двух разных сегментов в общем случае независимы друг от друга.
Например, экранная память может быть настроена так, что будет располагаться в области памяти, отличной от данных программы. Обычные указатели (внутри области данных программы) не будут иметь доступ к экрану.
Подобно 16-битной версии Watcom C (для 8086 и 80286), Watcom C/32 использует
ключевые слова __near
и __far
для описания
объектов, которые находятся либо в нормальном пространстве данных, либо в
другом.
Объекты или функции, которые являются ближними, требуют для доступа к ним 32-битный указатель.
Дальние объекты или функции требуют 48-битный указатель. Этот 48-битный указатель состоит из двух частей: 16-битного селектора и 32-битного смещения. Селектор похож на сегмент дальнего указателя 16-битной программы, за исключением того, что численное значение селектора не определяет напрямую область памяти. Вместо этого, для этого процессор использует значение селектора совместно с «таблицей дескрипторов». При обсуждении дальних указателей на процессоре 80386 термины селектор и сегмент можно считать синонимами.
Подобно 16-битному компилятору, компилятор Watcom C/32 поддерживает малую, компактную, среднюю и большую модели памяти. В последующих разделах подразумевается, что используется малая модель памяти, так как она самая подходящая.
__far
Watcom C/32
Ключевое слово __far
— квалификатор типа, который
модифицирует лексему, следующую за ней. Если __far
предшествует
*
(как в __far *
), то такой указатель указывает на
что-то дальнее (не в обычной области данных). Иначе, если __far
предшествует идентификатору объекта или декларируемой функции (как в
__far x
), то объект или функция — дальние.
Ключевое слово __far
может применяться только к именам функции
или объекта, либо символу оператора косвенности (указателя) *
.
Параметры функции не могут декларироваться как __far
, так
как они всегда находятся в обычной области данных.
Данные примеры демонстрируют использование ключевого слова __far
и подразумевают использование малой модели памяти.
int __far * ptr;
декларирует ptr
как указатель на целое. Объект ptr
— ближний, но целое, на которое он указывает, — дальнее.
int * __far fptr;
также декларирует fptr
как указатель на целое. Однако объект
fptr
— дальний, а целое, на которое он указывает, —
ближнее.
int __far * __far ffptr;
декларирует ffptr
как указатель (дальний) на целое (также
дальнее).
Если при декларации функции перед именем функции поместить ключевое слово
__far
, то компилятор будет считать её дальней. Важно, чтобы до
любого её вызова, который делается до её определения, был приведён
прототип функции. Например, декларация,
extern void __far SystemService();
объявляет функцию SystemService
как дальнюю, т.е. все вызовы
этой функции должны быть дальними.
Ещё несколько примеров:
extern struct systbl * __far FSysTblPtr( void );
декларирует функцию FSysTblPtr
как дальнюю, возвращающую
указатель на ближнюю структуру systbl
.
extern struct systbl __far * __far FFSysTblPtr( void );
декларирует функцию FFSysTblPtr
как дальнюю, возвращающую
указатель на дальнюю структуру systbl
.
extern void Indirect( char __far fn() );
декларируют функцию Indirect
как ближнюю, принимающую один
параметр fn
— указатель на дальнюю функцию, которая
возвращает char
.
extern int StoreSysTbl( struct systbl * __far sysptr );
является неверной декларацией, так как она пытается объявить
sysptr
как дальний. Все параметры должны быть ближними, так как
располагаются в обычной области данных, которая всегда — ближняя.
__near
Watcom C/32
Ключевое слово __near
— квалификатор типа, который
изменяет лексему, следующую за ним. Если __near
предшествует
*
(как в __near *
), то указатель указывает на нечто
ближнее (в обычной области данных). А если __near
предшествует
идентификатору объекта или декларируемой функции (как в __near
x
), то сам объект — ближний.
Ключевое слово __near
может применяться только к именам функции
или объекта, или к символу оператора косвенности (указателя) *
.
Программистам, которые используют малую модель памяти, ключевое слово
__near
не требуется, но может быть полезно для читабельности
программы.
__far16
и _Seg16
У процессора 80386 дальний указатель состоит из 16-битного селектора и 32-битного смещения. Кроме этого Watcom C/32 поддерживает специальный тип указателя, который состоит из 16-битного селектора и 16-битного смещения. Они называются указателями far16 и позволяют 32-битному коду иметь доступ к коду и данным работающим в 16-битном режиме.
В операционной системе OS/2 (версии 2.0 и выше) первые 512 мегабайт 4-х гигабайтного сегмента адресуемого при помощи регистра DS поделены на 8192 области областей по 64K байт каждая. Указатель far16 состоит из 16-битного селектора, ссылающегося на одну из таких областей, и 16-битного смещения в ней.
Для совместимости с компилятором Microsoft C Watcom C/32 предоставляет
ключевое слово __far16
. Декларация
тип __far16 * имя;
определяет объект, который является указателем far16. При обращении к такому указателю в 32-битном окружении, компилятор будет генерировать необходимый код для преобразования между указателем far16 и 32-битным «плоским».
Например,
char __far16 * bufptr;
декларирует объект bufptr
как указатель far16 на char
.
Функция, объявленная как
тип __far16 func( список параметров );
является 16-битной. Любой вызов подобной функции из 32-битного окружения
заставит компилятор конвертировать любой параметр — 32-битный указатель
— в указатель far16, а любой параметр типа int
из 32 бит в
16. (В 16-битном окружении объект типа int
занимает 16 бит.)
Любое возвращаемое из функции значение будет конвертировано в соответствующую
форму.
Например,
char * __far16 Scan( char * buffer, int buflen, short err );
декларирует 16-битную функцию Scan
. Когда это функция вызывается
из 32-битного окружения, параметр buffer
будет конвертирован из
плоского 32-битного указателя в указатель far16 (который в 16-битном
окружении будет декларирован как char __far *
). Параметр
buflen
будет конвертирован из 32-битного целого в 16-битное.
Параметр err
будет передан без изменений. После возврата
указатель far16 (дальний указатель в 16-битном окружении) будет конвертирован
в 32-битный указатель, который будет указывать туда же, но в 32-битном
адресном пространстве.
Для совместимости с IBM C Set/2 Watcom C/32 предоставляет ключевое слово
_Seg16
. Заметим, что _Seg16
не
взаимозаменяем с __far16
.
Декларация
тип * _Seg16 name;
определяет объект, который является указателем far16. Обратите внимание, что
_Seg16
находится по другую сторону от *
, чем
__far16
, как показано выше.
Например,
char * _Seg16 bufptr;
декларирует объект bufptr
как указатель far16 на char (как в
примере выше).
Ключевое слово _Seg16
не может быть использовано для описания
16-битных функций. Для этого нужно использовать директиву препроцессора
#pragma
. Подробнее см. «User's Guide». Функция
тип * _Seg16 func( список параметров );
определена как 32-битная, возвращающая указатель far16.
Например,
char * _Seg16 Scan( char * buffer, int buflen, short err );
декларирует 32-битную функцию Scan
. Преобразования типов не
будут производиться. Возвращаемое значение — указатель far16
.
Ближние указатели обычно — самый эффективный тип указателя, потому что они маленькие, и компилятор может взять на себя заботу о том, в какой сегмент памяти компьютера он указывает. (Ближний указатель является смещением). Дальние указатели являются более гибкими, потому что предоставляют программисту доступ к любой части памяти компьютера без ограничения отдельным сегментом. Однако они больше и медленнее из-за своей дополнительной гибкости.
Относительные указатели являются компромиссом между эффективностью ближних и
гибкостью дальних. Используя относительные указатели, программист должен
сообщать компилятору, какому сегменту принадлежит ближний указатель
(смещение), но может иметь доступ к сегментам памяти компьютера вне обычного
сегмента данных (DGROUP
). Результатом является указатель
маленький и почти столь же эффективный как ближний и с гибкостью почти, как у
дальнего.
Объект декларированный как относительный указатель попадает в одну из следующих категорий:
Для поддержки относительных указателей введены следующие ключевые слова:
__based __segment __segname __self
Вводится также следующий оператор:
:>
Эти ключевые слова и оператор описаны в последующих разделах.
Вводятся также два макро, определённые в <malloc.h>
:
_NULLSEG _NULLOFF
Они используются подобно константе NULL
, но с объектами
декларированными как __segment
и __based
соответственно.
Указатели и объекты относительно сегментной константы получают сегментное значение из указанного, именованного сегмента. Объекты декларируются следующим образом:
тип __based( __segname( "сегмент" ) ) имя-объекта;
а указатели так:
тип __based( __segname( "сегмент" ) ) * имя-объекта;
где сегмент — имя сегмента, в котором указатель или объект находятся. Как видно выше, имя сегмента — всегда строка. Есть три специальных имени сегмента, распознаваемых компилятором:
"_CODE" "_CONST" "_DATA"
"_CODE"
— сегмент кода по умолчанию. "_CONST"
— сегмент содержащий константы. "_DATA"
— сегмент
данных по умолчанию. Если имя сегмента не совпадает ни с одним из этих трёх
имён, то он будет создан с этим именем. Если определён объект относительно
сегментной константы, то он будет помещён в этот именованный сегмент. Если
определён указатель, то он будет указывать на объекты в этом именованном
сегменте.
Следующие примеры демонстрируют указатели и объекты относительно сегментной константы:
int __based( __segname( "_CODE" ) ) ival = 3; int __based( __segname( "_CODE" ) ) * iptr;
ival
является объектом, который находится в сегменте кода по
умолчанию. iptr
— объект, находящийся в сегменте данных
(обычном месте для объектов данных), но указывает на целое, находящееся в
сегменте кода. iptr
может указывать на ival
.
char __based( __segname( "GOODTHINGS" ) ) thing;
thing
является объектом, находящимся в сегменте
GOODTHINGS
, который будет создан, если не существует. (Создание
сегментов производится линкером и является методом группирования объектов и
функций. Неявно во время исполнения программы ничего не создаётся.)
Указатели относительно сегментного объекта получают сегментное значение из других именованных объектов. Указатели декларируются следующим образом:
тип __based( сегмент ) * имя;
где сегмент — объект типа __segment
.
Объект типа __segment
может содержать сегментное значение. Такие
объекты создаются конкретно для использования с указателями относительно
сегментного объекта.
Следующий пример демонстрирует указатель относительно сегментного объекта:
__segment seg; char __based( seg ) * cptr;
Объект seg
содержит только сегментное значение. Всякий раз,
когда объект cprt
используется для указания на символ,
действительное значение указателя будет вычислено из сегментного значения,
находящегося в seg
, и смещения, находящегося в
cptr
. Объекту seg
можно присваивать такие значения,
как:
_bheapseg
,
__segment
.
Для обращения к памяти свободные указатели должны явно комбинироваться с
сегментным значением. Они не берут сегментное значение из какого-либо другого
объекта. Для комбинации сегментного значения и свободного указателя
используется оператор :>
(по базе).
Например, на компьютерах IBM PC или PS/2 с цветным монитором под управлением
DOS, экранная память начинается с сегмента 0xB800
и смещения
0
. В текстовом режиме для получения первого символа на экране
можно использовать следующий код:
extern void main() { __segment screen; char __based( void ) * scrptr; screen = 0xB800; scrptr = 0; printf( "Левый верхний символ: '%c'.\n", *(screen:>scrptr) ); }
Обобщённая форма оператора :>
:
сегмент :> смещение
где сегмент — выражение типа __segment
, а
смещение — выражение типа __based( void ) *
.
Указатели относительные себя получают сегментное значение из самих себя. В частности, это удобно для структур, таких как связанные списки, когда все элементы списка находятся в одном сегменте. Указатель на один элемент списка можно использовать для доступа к следующему, поэтому для него компилятор будет использовать тот же сегмент, что и у исходного указателя.
Следующий пример демонстрирует функцию, которая печатает значения сохранённые в последних двух членах связанного списка:
struct a { struct a __based( __self ) * next; int number; }; extern void PrintLastTwo( struct a far * list ) { __segment seg; struct a __based( seg ) * aptr; seg = FP_SEG( list ); aptr = FP_OFF( list ); for( ; aptr != _NULLOFF; aptr = aptr->next ) { if( aptr->next == _NULLOFF ) { printf( "Последнее число: %d\n", aptr->number ); } else if( aptr->next->next == _NULLOFF ) { printf( "Предпоследнее число: %d\n", aptr->number ); } } }
Параметр функции PrintLastTwo
— дальний указатель на
связанный список, находящийся в любом месте памяти. Подразумевается, что все
члены конкретного списка находятся в одном сегменте памяти. (Другой экземпляр
списка может находится полностью в другом сегменте.) Объекту seg
присваивается сегментная часть дальнего указателя. Объекту aptr
— смещение, которое будет в сегменте seg
.
Выражение aptr->next
обращается к члену next
структуры хранящейся в памяти по смещению aptr
, в сегменте,
который извлечён для aptr
из значения seg
. До сих
пор поведение было похоже на то как, если бы next
был объявлен
как
struct a * next;
Выражение aptr->next->next
демонстрирует отличия в использовании
указателей относительных самих себя. Первая часть выражения
(aptr->next
) вычисляется как описано выше. Однако использование
результата для указания на следующий член происходит с использованием
смещения в члене next
и комбинированием его с сегментным
значением указателя использованного для доступа к этому члену, т.е.
aptr
, у которого сегментное значение seg
. Если
next
не декларировать с использованием __based( __self
)
, то вторая часть оператора будет ссылаться по смещению в члене
next
, но в сегменте данных по умолчанию (DGROUP
),
который может быть, а может и нет тем же сегментом, что и хранящийся в
seg
.
void
Тип void
предназначен для нескольких целей:
void * membegin;
определяет membegin
как указатель. Без оператора приведения типа он ни на что не указывает. Команда
*(char *) membegin = '\0';
поместит нуль в символ, на который указывает membegin
.
void rewind( FILE * stream );
декларирует стандартную библиотечную функцию rewind
, которая принимает один параметр и ничего не возвращает.
(void) getchar();
вызывает библиотечную функцию getchar
, которая обычно возвращает
символ. В данном случае символ отбрасывается, что приводит просто к
продвижению по файлу на один символ вперёд, без его передачи. Этот пример
использует void
только для читабельности, потому что приведение
результата выражения к типу void
делается автоматически.
Предыдущий пример можно написать так:
getchar();
Ключевое слово void
используется и в других случаях. Если
функция не имеет параметров, то в декларации можно использовать
void
. Например,
int getchar( void );
декларирует стандартную библиотечную функцию getchar
,
которая не имеет параметров и возвращает целое.
Ни один объект, кроме функции, не может быть декларированным типа void
.
const
и volatile
Объект может быть декларирован с ключевым словом const
. Такие
объекты не могут быть изменены программой напрямую. Объекты со статической
продолжительностью хранения, декларированные с этим квалификатором, могут
быть помещены компилятором, если он поддерживает эту возможность, в память
разрешённую только для чтения. Кроме того, это предоставляет компилятору
возможность отследить попытку изменения объекта. Компилятор, также, может
генерировать более качественный код, когда знает, что объект не будет
изменяться.
Даже если объект декларирован как константный, всё равно есть возможность его
изменить, сохранив адрес (с использованием оператора приведения к типу) в
другом объекте, указателе на тот же тип (не const
), а затем
использовав этот второй объект, чтобы изменить значение, на которое он
указывает. Однако делать это нужно очень осторожно, так как на компьютерах с
защищённой памятью это может привести к сбою.
Если декларация объекта не содержит *
(этот объект не
указатель), то присутствие ключевого слова const
в любом месте
спецификатора типа (включая любой typedef
) обозначает, что
объект — постоянный и не может быть изменён. Если объект —
указатель и const
находится слева от *
, то этот
объект — указатель на постоянное значение, которое нельзя изменять, но,
в тоже время, сам указатель изменять можно. Если const
—
справа от *
, то объект — постоянный указатель и изменять
его нельзя, но то, на что он указывает — можно. Если const
находится по обе стороны *
, то объект — постоянный
указатель на постоянное значение и изменять ни указатель, ни то, на что он
указывает, нельзя.
Если декларация структуры, объединения или массива содержит
const
, то каждый член такого типа при ссылке на него трактуется
так, будто было указано const
.
Декларации
const int baseyear = 1900; const int * byptr;
объявляют объект baseyear
как целое, чьё значение постоянно и
установлено в 1900, а byptr
как указатель на постоянный объект
целого типа. Если byptr
определён для указания на некоторое
целое, которое не декларировано как константное, то byptr
нельзя
использовать для изменения его значения. byptr
можно
использовать только для получения значения целого объекта, но не для его
изменения. Другой способ добиться этого — сделать то, на что указывает
byptr
константным, а byptr
— нет.
Декларации
int baseyear; int * const byptr = &baseyear;
объявляют объект byptr
как постоянный указатель на целое, в
данном случае на baseyear
. Значение baseyear
может
быть изменено при помощи byptr
, но само byptr
изменить нельзя. В этом случае сам byptr
— постоянный, но
то, на что он указывает — нет.
Любой объект может быть декларирован с ключевым словом volatile
.
Такие объекты могут свободно изменены программой, а также в результате
внешних воздействий. Например, при прерывании может быть установлен флаг.
volatile
говорит компилятору, что необходима особая осторожность
при оптимизации кода, который обращается к этому объекту, так, чтобы
поведение программы не изменялось. Объект, который компилятор может поместить
в регистр на длительное время, будет помещён в обычную память, и все
изменения, произведённые извне будут отражаться на поведении программы.
Если декларация объекта не содержит *
(этот объект не
указатель), то присутствие ключевого слова volatile
в любом
месте спецификатора типа (включая любой typedef
) обозначает, что
объект — изменчивый и может быть изменён в любое время без ведома
программы. Если объект — указатель и volatile
находится
слева от *
, то этот объект — указатель на изменчивое
значение, которое может в любое время измениться. Если volatile
— справа от *
, то объект — изменчивый указатель и
может измениться в любое время. Если volatile
находится по обе
стороны *
, то объект — изменчивый указатель на изменчивое
значение, а это значит, что и указатель, и значение могут измениться в любое
время.
Если декларация структуры, объединения или массива содержит
volatile
, то каждый член такого типа при ссылке на него
трактуется так, будто было указано volatile
.
Декларации
volatile int attncount; volatile int * acptr;
объявляют объект attncount
как целое, чьё значение может
измениться в любое время (например обработчиком асинхронного прерывания), а
объект acptr
как указатель на изменчивый объект целого типа.
Если const
и volatile
оба включены в декларацию
объекта, то программа не может его изменять, но он может быть изменён извне.
Примером такого объекта могут служить часы компьютера, которые периодически
обновляются (с каждым тиком), но программы не могут их изменять.