Kernighan, B. W. and Ritchie, D. M. "The 'C' Programming Language"; Chapter 17

17. Описания.

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

Описание:
спецификаторы-описания список-описателейнеоб;
Описатели в списке описателей содержат описываемые идентификаторы.Спецификаторы описания представляют собой последовательностьспецификаторов типа и спецификаторов класса памяти.

Спецификаторы-описания:
спецификатор-типа спецификаторы-описаниянеоб
спецификатор-класса-памяти спецификатор-описаниянеоб

Список должен быть самосогласованным в смысле, описываемом ниже.

Содержание

17.1. Спецификаторы класса памяти.
17.2. Спецификаторы типа.
17.3. Описатели.
17.4. Смысл описателей.
17.5. Описание структур и об'единений.
17.6. Инициализация.
17.7. Имена типов.
17.8. Typedef.


17.1. Спецификаторы класса памяти.

ниже перечисляются спецификаторы класса памяти:

Спецификатор-класса-памяти:
auto
static
extern
register
typedef

Спецификатор typedef не резервирует памяти и называется "спецификаторомкласса памяти" только по синтаксическим соображениям; это обсуждается вп. 17.8. Смысл различных классов памяти был обсужден в п. 13.

Описания auto, static и register служат также в качестве определений втом смысле, что они вызывают резервирование нужного количества памяти.В случае extern должно присутствовать внешнее определение (п. 19)указываемых идентификаторов гдe-то вне функции, в которой они описаны.

Описание register лучше всего представлять себе как описание autoвместе с намеком компилятору, что описанные таким образом переменныебудут часто использоваться. Эффективны только несколько первых такихописаний. Кроме того, в регистрах могут храниться только переменныеопределенных типов; на pdp-11 это int, char или указатель. Существуети другое ограничение на использование регистровых переменных: к нимнельзя применять операцию взятия адреса &. При разумном использованиирегистровых описаний можно ожидать получения меньших по размеру иболее быстрых программ, но в будующем улучшение генерирования кодовможет сделать их ненужными.

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


17.2. Спецификаторы типа.

Ниже перечисляются спецификаторы типа.

Спецификатор-типа:
char
short
int
long
unsigned
float
double
спецификатор-структуры-или-об'единения
определяющеe-тип-имя

Слова long, short и usigned можно рассматривать как прилагательные;допустимы следующие комбинации:

  short int  long int  usigned int  long float
Последняя комбинация означает то же, что и double. В остальномописание может содержать не более одного спецификатора типа. Еслиописание не содержит спецификатора типа, то считается, что он имеетзначение int.

Спецификаторы структур и об'единений обсуждаются в п. 17.5; описания с определяющими тип именами typedef обсуждаются в п. 17.8.


17.3. Описатели.

Входящий в описание список описателей представляет собойпоследовательность разделенных запятыми описателей, каждый из которыхможет иметь инициализатор.

Список-описателей:
инициализируемый-описатель
инициализируемый-описатель, список-описателей
инициализируемый-описатель:
описатель-инициализаторнеоб

Инициализаторы описываются в п. 17.6. Спецификаторы и описанияуказывают тип и класс памяти об'ектов, на которые ссылаются описатели.Описатели имеют следующий синтаксис:

Описатель:
идентификатор
( описатель )
* описатель
описатель ()
описатель [константноe-выражениенеоб]

Группирование такое же, как и в выражениях.


17.4. Смысл описателей.

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

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

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

Представим себе описание

 t di
где t - спецификатор типа (подобный int и т.д.), а di - описатель.Предположим, что это описание приводит к тому, что соответствующийидентификатор имеет тип "...t", где "..." пусто, если di простоотдельный идентификатор (так что тип х в "int х" просто int). Тогда, если di имеет форму
 *d
то содержащийся идентификатор будет иметь тип "... Указатель на t".

Если di имеет форму

 d()
то содержащийся идентификатор имеет тип "... Функция, возвращающая t".

Если di имеет форму

 d[константноe-выражение]
или
 d[ ]
то содержащийся идентификатор имеет тип "...массив t". В первом случаеконстантным выражением является выражение, значение которого можноопределить во время компиляции и которое имеет тип int. (точноеопределение константного выражения дано в п. 24). Когда несколькоспецификаций вида "массив из" оказываются примыкающими, то создаетсямногомерный массив; константное выражение, задающее границы массивов,может отсутствовать только у первого члена этой последовательности.Такое опускание полезно, когда массив является внешним и егофактическое определение, которое выделяет память, приводится в другомместе. Первое константное выражение может быть опущено также тогда,когда за описателем следует инициализация. В этом случае размеропределяется по числу приведенных инициализируемых элементов.

Массив может быть образован из элементов одного из основных типов, изуказателей, из структур или об'единений или из других массивов (чтобыобразовать многомерный массив).

Не все возможности, которые разрешены с точки зрения указанного вышесинтаксиса, фактически допустимы. Имеются следующие ограничения:функции не могут возвращать массивы, структуры, об'единения илифункции, хотя они могут возвращать указатели на такие вещи; несуществует массивов функций, хотя могут быть массивы указателей нафункции. Аналогично, структуры или об'единения не могут содержатьфункцию, но они могут содержать указатель на функцию.

В качестве примера рассмотрим описание

 int i, *ip, f(), *fip(), (*pfi)();
в котором описывается целое i, указатель ip на целое, функция f,возвращающая целое, функция fip, возвращающая указатель на целое,и указатель pfi на функцию, которая возвращает целое. Особенно полезносравнить два последних описателя. Связь в *fip() можно представить ввиде *(fip()), так что описанием предполагается, а такой жеконструкцией в выражении требуется обращение к функции fip и последующееиспользование косвенной адресации для выдачи с помощью полученногорезультата (указателя) целого. В описателе (*pfi)() дополнительныескобки необходимы, поскольку они точно так же, как и в выражении,указывают, что косвенная адресация через указатель на функцию выдаетфункцию, которая затем вызывается; эта вызванная функция возвращаетцелое.

В качестве другого примера приведем описание

  float fa[17], *afp[17];
в котором описывается массив чисел типа float и массив указателей начисла типа float. Наконец,
 static int x3d[3][5][7];
описывает статический трехмерный массив целых размером 3*5*7. Болееподробно, x3d является массивом из трех элементов; каждый элементявляется массивом пяти массивов; каждый последний массив являетсямассивом из семи целых. Каждое из выражений x3d, x3d[i], x3d[i][j]и x3d[i][j][k] может разумным образом появляться в выражениях. Первыетри имеют тип "массив", последнее имеет тип int.


17.5. Описание структур и об'единений.

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

Спецификатор структуры-или-об'единения:
структура-или-об'единение { список-описаний-структуры }
идентификатор-структуры-или-об'единения { список-описаний-структуры }
идентификатор-структуры-или-об'единения

Структура-или-об'единение:
struct
union

Список-описаний-структуры является последовательностью описаний членовструктуры или об'единения:

Список-описаний-структуры:
описаниe-структуры
описаниe-структуры список-описаний-структуры

Описаниe-структуры:
спецификатор-типа список-описателей-структуры

Список-описателей-структуры:
описатель-структуры
описатель-структуры, список-описателей-структуры

В обычном случае описатель структуры является просто описателем членаструктуры или об'единения. Член структуры может также состоять изспецифицированного числа битов. Такой член называется также полем;его длина отделяется от имени поля двоеточием.

Описатель-структуры:
описатель
описатель: константное выражение
: константное выражение

Внутри структуры описанные в ней об'екты имеют адреса, которыеувеличиваются в соответствии с чтением их описаний слева направо.Каждый член структуры, который не является полем, начинается садресной границы, соответствующей его типу; следовательно вструктуре могут оказаться неименованные дыры. Члены, являющиеся полями,помещаются в машинные целые; они не перекрывают границы слова. Поле,которое не умещается в оставшемся в данном слове пространстве,помещается в следующее слово. Поля выделяются справа налево на pdp-11и слева направо на других машинах.

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

Сам язык не накладывает ограничений на типы об'ектов, описанных какполя, но от реализаций не требуется обеспечивать что-либо отличное отцелых полей. Более того, даже поля типа int могут рассматриваться какнеимеющие знака. На pdp-11 поля не имеют знака и могут принимать толькоцелые значения. Во всех реализациях отсутствуют массивы полей и к полямне применима операция взятия адреса &, так что не существует иуказателей на поля.

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

Спецификатор структуры или об'единения во второй форме, т.е. один из

  • struct идентификатор {список-описаний-структуры}
  • union идентификатор {список-описаний-структуры}
описывает идентификатор в качестве ярлыка структуры(или ярлыкаоб'единения) структуры, специфицированной этим списком. Последующееописание может затем использовать третью форму спецификатора, один из
  • struct идентификатор
  • union идентификатор
Ярлыки структур дают возможность определения структур, которыессылаются на самих себя; они также позволяют неоднократно использоватьприведенную только один раз длинную часть описания. Запрещаетсяописывать структуру или об'единение, которые содержат образец самогосебя, но структура или об'единение могут содержать указатель наструктуру или об'единение такого же вида, как они сами.

Имена членов и ярлыков могут совпадать с именами обычных переменных.Однако имена ярлыков и членов должны быть взаимно различными.

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

Вот простой пример описания структуры:

 struct tnode {        char tword[20];        int count;        struct tnode *left;        struct tnode *right; };
Такая структура содержит массив из 20 символов, целое и два указателяна подобные структуры. Как только приведено такое описание, описание
 struct tnode s, *sp;
говорит о том, что s является структурой указанного вида, а sp являетсяуказателем на структуру указанного вида. При наличии этих описанийвыражение
 sp->count
ссылается к полю count структуры, на которую указывает sp; выражение
 s.left
ссылается на указатель левого поддерева в структуре s, а выражение
 s.right->tword[0]
ссылается на первый символ члена tword правого поддерева из s.


17.6. Инициализация.

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

Инициализатор:
= выражение
= {список-инициализатора}
= {список-инициализатора,}

Список-инициализатора:
выражение
список-инициализатора, список-инициализатора
{список-инициализатора}

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

Гарантируется, что неинициализированные статические и внешниепеременные получают в качестве начальных значений 0;неинициализированныеавтоматические и регистровые переменные в качестве начальных значенийсодержат мусор.

Когда инициализатор применяется к скаляру (указателю или об'ектуарифметического типа), то он состоит из одного выражения, возможнозаключенного в фигурные скобки. Начальное значение об'екта находится извыражения; выполняются те же самые преобразования, что и приприсваивании.

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

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

Последнее сокращение допускает возможность инициализации массива типаchar с помощью строки. В этом случае члены массива последовательноинициализируются символами строки.

Например,

 int x[] = {1,3,5};
описывает и инициализирует х как одномерный массив; поскольку размермассива не специфицирован, а список инициализитора содержит триэлемента, считается, что массив состоит из трех членов.

Вот пример инициализации с полным использованием фигурных скобок:

 float *y[4][3] = { { 1, 3, 5 }, { 2, 4, 6 }, { 3, 5, 7 }, };
Здесь 1, 3 и 5 инициализируют первую строку массива y[0], а именноy[0][0], y[0][1] и y[0][2]. Аналогичным образом следующие две строчкиинициализируют y[1] и y[2]. Инициализатор заканчивается преждевременно,и, следовательно массив y[3] инициализируется нулями. В точноститакого же эффекта можно было бы достичь, написав
 float y[4][3] = { 1, 3, 5, 2, 4, 6, 3, 5, 7 };
Инициализатор для y начинается с левой фигурной скобки, ноинициализатора для y[0] нет. Поэтому используется 3 элемента из списка.Аналогично следующие три элемента используются последовательно для y[1]и y[2]. Следующее описание
 float y[4][3] = { {1}, {2}, {3}, {4} };
инициализирует первый столбец y (если его рассматривать как двумерныймассив), а остальные элементы заполняются нулями.

И наконец, описание

  char msg[] = "syntax error on line %s\n";
демонстрирует инициализацию элементов символьного массива с помощьюстроки.


17.7. Имена типов.

В двух случаях (для явного указания типа преобразования в конструкцииперевода и для аргументов операции sizeof) желательно иметьвозможность задавать имя типа данных. Это осуществляется с помощью"имени типа", которое по существу является описанием об'екта такого типа, в котором опущено имя самого об'екта.

Имя типа:
спецификатор-типа абстрактный-описатель

Абстрактный-описатель:
пусто
(абстрактный-описатель)
* абстрактный описатель
абстрактный-описатель ()
абстрактный-описатель [константное выражениенеоб]

Во избежании двусмысленности в конструкции

 (абстрактный описатель)
требуется, чтобы абстрактный-описатель был непуст. При этом ограничениивозможно однозначено определить то место в абстрактном-описателе, гдедолжен появитьсяидентификатор, если бы эта конструкция была описателем вописании. Именованный тип совпадает тогда с типом гипотетическогоидентификатора. Например, имена типов
 int int * int *[3] int (*)[3] int *() int (*)()
именуют соответственно типы "целый", "указатель на целое", "массив изтрех указателей на целое", "указатель на массив из трех целых", "функция, возвращающая указатель на целое" и "указатель на функцию,возвращающую целое".


17.8. Typedef.

Описания, в которых "класс памяти" специфицирован как typedef, невызывают выделения памяти. Вместо этого они определяют идентификаторы,которые позднее можно использовать так, словно они являются ключевымисловами, имеющими основные или производные типы.

 Определяющее-тип-имя  идентификатор
В пределах области действия описания со спецификатором typedef каждыйидентификатор, являющийся частью любого описателя в этом описании,становится синтаксически эквивалентным ключевому слову, имеющему тоттип, который ассоциирует с идентификатором в описанном в п. 17.4 смысле.Например, после описаний
 typedef int miles, *klicksp; typedef struct { double re, im; } complex;
конструкции
 miles distance; extern klicksp metricp; complex z, *zp;
становятся законными описаниями; при этом типом distance является int,типом metricp - "указатель на int", типом z - специфицированнаяструктура и типом zp - указатель на такую структуру.

Спецификатор typedef не вводит каких-либо совершенно новых типов, атолько определяет синонимы для типов, которые можно было быспецифицировать и другим способом. Так в приведенном выше примерепеременная distance считается имеющей точно такой же тип, что и любойдругой об'ект, описанный в int.