Содержание

Выводимые типы

Далее обсуждаются следующие темы:

Структуры

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

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

В 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. Тип объекта tokenstruct 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 содержит следующие члены:

  1. link является указателем на структуру list_el, показывая, что, возможно, экземпляры этой структуры будут использоваться в связанном списке,
  2. elnum — беззнаковое короткое целое,
  3. length — беззнаковое битовое поле занимающее 3 бита и допускающее значения от 0 до 7,
  4. offset — знаковое битовое поле, занимающее 4 бита, которое будет помещено в целое вместе с length. Так как его тип — signed int, то его значения будут в диапазоне от -8 до 7,
  5. flag — 1-битное поле,

    Так как тип — int, то компиляторы Watcom C/16 и C/32 будут трактовать этот бит как знаковый, и значения битового поля будут -1 и 0.

  6. 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

Примечание: следующий раздел касается только компилятора 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.

Специальные типы указателей Watcom C/32

На процессоре 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.

Относительные указатели Watcom C/16 и C/32

Ближние указатели обычно — самый эффективный тип указателя, потому что они маленькие, и компилятор может взять на себя заботу о том, в какой сегмент памяти компьютера он указывает. (Ближний указатель является смещением). Дальние указатели являются более гибкими, потому что предоставляют программисту доступ к любой части памяти компьютера без ограничения отдельным сегментом. Однако они больше и медленнее из-за своей дополнительной гибкости.

Относительные указатели являются компромиссом между эффективностью ближних и гибкостью дальних. Используя относительные указатели, программист должен сообщать компилятору, какому сегменту принадлежит ближний указатель (смещение), но может иметь доступ к сегментам памяти компьютера вне обычного сегмента данных (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 можно присваивать такие значения, как:

Свободные указатели

Для обращения к памяти свободные указатели должны явно комбинироваться с сегментным значением. Они не берут сегментное значение из какого-либо другого объекта. Для комбинации сегментного значения и свободного указателя используется оператор :> (по базе).

Например, на компьютерах 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 предназначен для нескольких целей:

  1. Для декларации указателя на обобщённый тип. Например,
    void * membegin;
    

    определяет membegin как указатель. Без оператора приведения типа он ни на что не указывает. Команда

    *(char *) membegin = '\0';
    

    поместит нуль в символ, на который указывает membegin.

  2. Для декларации функции не возвращающей значение. Например,
    void rewind( FILE * stream );
    

    декларирует стандартную библиотечную функцию rewind, которая принимает один параметр и ничего не возвращает.

  3. Для вычисления выражений с побочными эффектами, отбрасывая результат выражения. Например,
    (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 оба включены в декларацию объекта, то программа не может его изменять, но он может быть изменён извне. Примером такого объекта могут служить часы компьютера, которые периодически обновляются (с каждым тиком), но программы не могут их изменять.