Класс памяти объекта описывает:
Спецификатор класса памяти — один из:
auto
register
extern
static
typedef
typedef
включён в этот список для удобства, так как синтаксис
определения типа такой же, как декларация объекта. Декларация
typedef
создаёт не объект, а только синоним типа, который не
имеет класса памяти связанного с ним.
При декларации объекта может одновременно использоваться только одно ключевое
слово (кроме typedef
).
Если объект или функция декларированы с использованием класса памяти, но без
указания спецификатора типа, то тип объекта или функции будет
int
.
Так как спецификатор класса памяти может быть помещён после спецификатора типа, то это может затруднить чтение. Рекомендуется класс памяти (если он есть) всегда помещать первым в декларации. Стандарт ANSI считает возможность помещать спецификатор класса памяти иначе, чем в самом начале декларации, устаревающей.
Декларация typedef
вводит синоним для какого-либо типа. Новый
тип не вводится.
Обобщённая форма определения типа:
typedef информация-о-типе typedef-имя;
typedef-имя может быть списком идентификаторов, разделённых запятыми, каждый из которых становится синонимом типа. Имена находятся в том же пространстве, что и обычные объекты, и могут быть переопределены во вложенных блоках. Однако это может привести к путанице и лучше этого избегать.
Простая декларация
typedef signed int COUNTER;
объявляет идентификатор COUNTER
эквивалентным типу signed int
. Последующая декларация, вроде
COUNTER ctr;
объявляет объект ctr
как знаковое целое. Если позже будет
необходимо сделать все счётчики длинными знаковыми целыми, то изменить нужно
будет только typedef
:
typedef long signed int COUNTER;
Все декларации объектов этого типа будут использовать новый тип.
typedef
можно использовать для упрощения других деклараций.
Например, рассмотрим следующую структуру:
struct complex { double real; double imaginary; };
Для объявления экземпляра этой структуры необходима следующая декларация:
struct complex cnum;
А теперь рассмотрим определение структуры с определением типа:
typedef struct { double real; double imaginary; } COMPLEX;
В этом случае идентификатор COMPLEX
обозначает собой всё
определение структуры, включая ключевое слово struct
. Поэтому
объект может быть декларирован как
COMPLEX cnum;
Этот простой пример демонстрирует создание более читабельных деклараций объектов.
Рассмотрим следующий пример, в котором объект fnptr
декларирован
как указатель на функцию, которая имеет два параметра — указатель на
структуру dim3
и целое. Функция возвращает указатель на
структуру dim3
. Декларации могут быть такими:
struct dim3 { int x; int y; int z; }; struct dim3 * (*fnptr)( struct dim3 *, int );
или такими:
typedef struct { int x; int y; int z; } DIM3; DIM3 * (*fnptr)( DIM3 *, int );
или такими:
typedef struct { int x; int y; int z; } DIM3; typedef DIM3 * DIM3FN( DIM3 *, int ); DIM3FN * fnptr;
Последний пример просто декларирует fnptr
как указатель на
DIM3FN
, при этом DIM3FN
определён как функция с
двумя параметрами, указателем на DIM3
и целым. Функция
возвращает указатель на DIM3
. DIM3
—
структура, содержащая три координаты.
Некоторые операторы, такие как присвоение, могут оперировать двумя объектами только одинакового типа. Если оба операнда уже одного типа, то не требуется специальных преобразований. В противном случае, компилятор может автоматически изменить один или оба операнда, чтобы сделать их одинаковыми. Примером этого служат целочисленное расширение и арифметические преобразования. Другие типы могут потребовать явного преобразования.
Компилятор принимает решение о том, нужно ли явное преобразование типов на основе концепции совместимых типов. Следующие типы совместимы:
unsigned long int
и int long unsigned
.
",..."
), над которыми не производилось преобразование типов по
умолчанию,
Объекты со статической продолжительностью хранения создаются и
инициализируются только раз, до исполнения программы. Любое значение,
хранящееся в таком объекте, остаётся без изменения до тех пор, пока не будет
явно изменено программой (или из вне, если оно декларировано с ключевым
словом volatile
).
Любой объект, декларированный вне функции, имеет статическую продолжительность хранения.
Есть три типа статических объектов:
extern void Fn( int x ) { static int ObjCount; /* ... */ }
static int ObjCount; extern void Fn( int x ) { /* ... */ }
extern int ObjCount = { 0 }; extern void Fn( int x ) { /* ... */ }
Первые два типа определяются с ключевым словом static
,
а третий — с необязательным словом extern
.
static
Любой декларации объекта может предшествовать ключевое слово
static
. Декларация внутри функции говорит компилятору, что
объект не имеет связи, то есть доступен только внутри функции.
Декларация вне функции говорит, что объект имеет внутреннюю связь, то
есть доступен всем функциям внутри модуля, где он определён. Другие модули
ссылаться на него не могут. Они могут иметь свои собственные объекты с тем же
именем, но это вопрос программистской практики и лучше этого избегать.
Значение объекта будет сохраняться между вызовами функций. Любое значение, помещенное в объект со статической продолжительностью хранения будет неизменным, до того как его изменит функция внутри того же модуля. В то же время указатель на объект можно передать функции вне модуля, в котором он определён. Этот указатель можно использовать для изменения значения объекта.
extern
Если объект декларирован внутри функции с ключевым словом
extern
, то он имеет внешнюю связь, то есть его значение
доступно для всех модулей и функций, содержащих такое определение в данном
модуле. В данном случае нельзя указывать инициализатор, так как память для
объекта выделяется в другом модуле.
Если объект декларирован вне определения функции, и декларация не содержит
ключевых слов static
или extern
, то память для
объекта выделяется в данном месте. Объект имеет внешнюю связь, то есть
доступен другим модулям программы.
Следующие примеры демонстрируют создание внешних объектов. Декларации произведены вне какой-либо функции:
int X; float F;
Если декларация объекта вне определения функции содержит ключевое слово
extern
и список инициализаторов, то память для него выделяется в
этом месте, и объект имеет внешнюю связь. Однако, если в декларацию не входит
список инициализаторов, то компилятор подразумевает, что объект декларирован
в другом месте. Если при компиляции оставшейся части модуля не будут найдены
дальнейшие декларации объекта, либо будут найдены декларации с
extern
без списка инициализаторов, то память для него будет
выделяться в другом модуле. Если последующие декларации в том же модуле имеют
список инициализаторов или у них отсутствует ключевое слово
extern
, то память для объекта будет выделяться в этом месте.
Следующие примеры также демонстрируют создание внешних объектов:
extern LIST * ListHead = 0; int StartVal = 77;
Следующие примеры демонстрируют пробное определение внешних объектов. Если не будет найдено последующих определений объекта в виде, приведённом выше, то объект находится вне этого модуля.
extern LIST * ListEl; extern int Z;
Другой модуль может определять свой собственный объект с таким же именем (с
классом памяти static
), но ему будет недоступен другой, с
внешней связь. Это может привести к путанице и является сомнительной
программистской практикой.
Любое значение помещённое в объект, декларированный с ключевым словом
extern
останется неизменным до момента, когда его изменит
функция из этого же или другого модуля.
Функция декларированная без ключевого слова static
имеет внешнюю связь.
Предположим, в модуле декларирован объект (вне любого определения функции):
struct list_el * ListTop;
где структура list_el
определена где-то в другом месте. Эта
декларация резервирует память для объекта ListTop
с внешней
связью и объявляет его указателем на структуру list_el
.
Декларация в другом модуле
extern struct list_el * ListTop;
ссылается на тот же объект ListTop
и утверждает, что он
находится вне этого модуля.
Внутри программы, возможно состоящей из нескольких модулей, каждый объект или функция с внешней связью должны быть определены (выделена память) только раз.
Наиболее часто в C используются объекты, которые имеют значение только в функции, где они определены. Объект создается в момент начала работы функции и уничтожается, когда она завершается. Про такие объекты говорят, что они имеют автоматическую продолжительность хранения. Область видимости упомянутых объектов — функция, в которой они определены.
Если такой объект имеет тоже имя, что и объект определённый вне функции (с
использованием static
или extern
), то внешний
объект будет скрыт для функции.
Внутри функции любой объект, которому в декларации не предшествует ключевое
слово static
или extern
, имеет автоматическую
длительность хранения.
Возможно декларировать объект как автоматический внутри любого блока функции. Область видимости такого объекта — блок, внутри которого он объявлен, включая блоки внутри его. Любой внешний блок не будет иметь доступа к такому объекту.
Автоматические объекты могут быть инициализированы, как это описано в разделе Инициализация объектов. Инициализация объекта будет происходить только при нормальном входе в блок, в котором он определён. В частности, принудительный переход в блок вложенный внутри функции не будет инициализировать какие-либо объекты определённые в нём. Это вопросительная программистская практика и лучше её избегать.
Следующая функция проверяет строку, пустая она или содержит цифры:
extern int IsInt( const char * ptr ) /**********************************/ { if( *ptr == '\0' ) return( 0 ); for( ;; ) { char ch; ch = *(ptr++); if( ch == '\0' ) return( 1 ); if( !isdigit( ch ) ) return( 0 ); } }
Объект ch
имеет область видимости только внутри цикла. Любая
команда до или после цикла не может к нему обращаться.
auto
В функции декларация объекта, которая не содержит ключевых слов
static
, extern
или register
объявляет
объект с автоматической продолжительностью хранения. Для читабельности,
декларации такого объекта может предшествовать ключевое слово
auto
.
Объект, декларированный без указания спецификатора класса памяти или с
указанием auto
— «адресуемый», т.е. к нему можно применять
оператор взятия адреса.
Программист не должен делать каких-либо предположений о месте автоматических объектов в памяти, декларированных в функции. Если важно их относительное расположение, то необходимо использовать структуру.
Следующая функция демонстрирует использование автоматических объектов:
extern int FindSize( struct thing * thingptr ) /********************************************/ { auto char * start; auto char * finish; FindEnds( thingptr, &start, &finish ); return( finish - start + 1 ); }
Адреса автоматических объектов start
и finish
передаются функции FindEnds
, которая, возможно, изменяет их.
register
Объект, декларированный внутри функции, и чья декларация включает ключевое
слово register
, имеет автоматическую продолжительность хранения.
Ключевое слово register
просто намекает компилятору, что этот
объект будет часто использоваться, разрешая попробовать поместить его в
память быстрого доступа, такую как регистр процессора. Компилятор, однако,
может игнорировать такую директиву по каким-либо причинам, таким как,
В регистры могут быть помещены объекты только некоторых определённых типов, однако набор таких типов зависит от реализации.
Компиляторы Watcom C/16 и C/32 могут поместить любой достаточно маленький объект, включая небольшую структуру, в один или более регистров.
Компилятор сам принимает решение о том, какие объекты будут помещены в
регистры. Ключевое слово register
игнорируется, за
исключением предотвращения получения адреса такого объекта.
Объекты декларированные с или без ключевого слова register
могут
означать одно и тоже. Исключением из этого правила служит то, что к
регистровым объектам не может применяться оператор взятия адреса
(&
), так как в большинстве случаев, регистры находятся
отдельно от обычной памяти.