23. Снова о типах.

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

Содержание

23.1. Структуры и об'единения.
23.2. Функции.
23.3. Массивы, указатели и индексация.
23.4. Явные преобразования указателей.


23.1. Структуры и об'единения.

Только две вещи можно сделать со структурой или об'единением: назвать один из их членов (с помощью операции) или извлечь их адрес ( с помощью унарной операции &). Другие операции, такие как присваивание им или из них и передача их в качестве параметров, приводят к сообщению об ошибке. В будущем ожидается, что эти операции, но не обязательно какиe-либо другие, будут разрешены.

В п. 16.1 говорится, что при прямой или косвенной ссылке на структуру (с помощью . или ->) имя справа должно быть членом конструкции, названной или указанной выражением слева. Это ограничение не навязывается строго компилятором, чтобы дать возможность обойти правила типов. В действительности перед '.' допускается любое l-значение и затем предполагается, что это l-значение имеет форму структуры, для которой стоящее справа имя является членом. Таким же образом, от выражения, стоящего перед '->', требуется только быть указателем или целым. В случае указателя предполагается, что он указывает на структуру, для которой стоящее справа имя является членом. В случае целого оно рассматривается как абсолютный адрес соответствующей структуры, заданный в единицах машинной памяти.

Такие структуры не являются переносимыми.


23.2. Функции.

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


 int f();
 ...
 g(f);
Тогда определение функции g могло бы выглядеть так:

 g(funcp)
 int(*funcp)();
 {
        ...
        (*funcp)();
        ...
 }
Обратите внимание, что в вызывающей процедуре функция f должна быть описана явно, потому что за ее появлением в g(f) не следует скобка ( .


23.3. Массивы, указатели и индексация.

Каждый раз, когда идентификатор, имеющий тип массива, появляется в выражении, он преобразуется в указатель на первый член этого массива. Из-за этого преобразования массивы не являются l-значениями. По определению операция индексация [] интерпретируется таким образом, что e1[e2] считается идентичным выражению *((e1)+(e2)). Согласно правилам преобразований, применяемым при операции +, если e1 - массив, а e2 - целое, то el[e2] ссылается на e2-й член массива e1. Поэтому, несмотря на несимметричный вид, операция индексации является коммутативной.

В случае многомерных массивов применяется последовательное правило. Если е является n-мерным массивом размера i*j*...*к, то при появлении в выражении е преобразуется в указатель на (n-1)-мерный массив размера j*...*к. Если операция * либо явно, либо неявно, как результат индексации, применяется к этому указателю, то результатом операции будет указанный (n-1)-мерный массив, который сам немедленно преобразуется в указатель.

Рассмотрим, например, описание


 int x[3][5];
Здесь x массив целых размера 3*5. При появлении в выражении х P преобразуется в указатель на первый из трех массивов из 5 целых. В выражении x[i], которое эквивалентно *(x+i), сначала x преобразуется в указатель так, как описано выше; затем i преобразуется к типу x, что вызывает умножение i на длину об'екта, на который указывает указатель, а именно на 5 целых об'ектов. Результаты складываются, и применение косвенной адресации дает массив (из 5 целых), который в свою очередь преобразуется в указатель на первое из этих целых. Если в выражение входит и другой индекс, то таже самая аргументация применяется снова; результатом на этот раз будет целое.

Из всего этого следует, что массивы в языке "C" хранятся построчно ( последний индекс изменяется быстрее всего) и что первый индекс в описании помогает определить общее количество памяти, требуемое для хранения массива, но не играет никакой другой роли в вычислениях, связанных с индексацией.


23.4. Явные преобразования указателей.

Разрешаются определенные преобразования, с использованием указателей , но они имеют некоторые зависящие от конкретной реализации аспекты. Все эти преобразования задаются с помощью операции явного преобразования типа; см. п. 16.2 и 17.7.

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

Об'ект целочисленного типа может быть явным образом преобразован в указатель. Такое преобразование всегда переводит преобразованное из указателя целое в тот же самый указатель, но в других случаях оно будет машинно-зависимым.

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

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


 extern char *alloc();
 double *dp;
 dp=(double*) alloc(sizeof(double));
 *dp=22.0/7.0;
Функция alloc должна обеспечивать (машинно-зависимым способом), что возвращаемое ею значение будет подходящим для преобразования в указатель на double; в таком случае использование этой функции будет переносимым.

Представление указателя на pdp-11 соответствует 16-битовому целому и измеряется в байтах. Об'екты типа char не имеют никаких ограничений на выравнивание; все остальные об'екты должны иметь четные адреса.

На honeywell 6000 указатель соответствует 36-битовому целому; слову соответствует 18 левых битов и два непосредственно примыкающих к ним справа бита, которые выделяют символ в слове. Таким образом, указатели на символы измеряются в единицах 2 в степени 16 байтов; все остальное измеряется в единицах 2 в степени 18 машинных слов. Величины типа double и содержащие их агрегаты должны выравниваться по четным адресам слов (0 по модулю 2 в степени 19). Эвм ibm 370 и interdata 8/32 сходны между собой. На обеих машинах адреса измеряются в байтах; элементарные об'екты должны быть выровнены по границе, равной их длине, так что указатели на short должны быть кратны двум, на int и float - четырем и на double - восьми. Агрегаты выравниваются по самой строгой границе, требуемой каким-либо из их элементов.