Если бы переменная i не была бы инициализирована,она бы была автоматически установлена в 0 при линковании. В этом случае напечатанные значения были бы равны 1, 2 и 3.
Объявление переменной на внутреннем уровне
Любой из четырех спецификаторов класса памяти может быть использован для объявления переменной на внутреннем уровне. Если спецификатор класса памяти опускается в объявлении переменной на внутреннем уровне, то подразумевается класс памяти auto.
Спецификатор класса памяти auto объявляет переменную с локальным временем жизни. Переменная видима только в том блоке, где она объявлена. Объявления переменных auto могут включать инициализаторы. Переменные класса памяти auto автоматически не инициализируются, а инициализируются явно при объявлении или присваивании начальных значений, посредством операторов внутри блока. Если нет инициализации, то величина переменной auto считается неопределенной.
Спецификатор класса памяти register сообщает компилятору о том, чтобы он распределил память под переменную в регистре, если это возможно. Использование регистровой памяти обычно приводит к более быстрому времени доступа и к меньшему размеру результирующего кода. Переменные, объявленные с классом памяти register имеют ту же самую видимость, что и переменные auto.
Число регистров, которое может быть использовано под память переменных, зависит от машины. Когда компилятор встречает спецификатор класса памяти register в объявлении, а свободного регистра не имеется, то для переменной распределяется память класса auto. Компилятор назначает переменным регистровую память в том порядке, в котором появляются объявления в исходном файле. Регистровая память, если она имеется, гарантирована только для целого и адресного типов.
Переменная, объявленная на внутреннем уровне со спецификатором класса памяти static,имеет глобальное время жизни и имеет видимость только внутри блока, в котором она объявлена. В отличие от переменных auto, переменные, объявленные как static, сохраняют свое значение при завершении блока.
Переменные класса памяти static могут быть инициализированы константным выражением. Если явной инициализации нет, то переменная класса памяти static автоматически устанавливается в 0. Инициализация выполняется один раз во время компиляции. Инициализация переменной класса памяти static не повторяется при новом входе в блок.
Переменная, объявленная со спецификатором класса памяти extern, является ссылкой на переменную с тем же самым именем, определенную на внешнем уровне в любом исходном файле программы.
Цель внутреннего объявления extern состоит в том, чтобы
сделать определение переменной внешнего уровня видимой внутри блока. Внутреннее об'явление extern не изменяет видимость глобальной переменной в любой другой части программы.
Пример:
int i = 1;
main()
{ /* reference to i, defined above */
extern int i;
/* initial value is zero; a is
visible only within main */
static int a;
/* b is stored in a register, if possible */ register int b = 0;
/* default storage class is auto */
int c = 0;
/* values printed are 1, 0, 0, 0 */ printf("%d\n%d\n%d\n%d\n", i, a, b, c);
other();
}
other()
{
/* i is redefined */
int i = 16;
/* this a is visible only within other */
static int a = 2;
a += 2;
/* values printed are 16, 4 */
printf("%d\n%d\n", i, a);
}
Переменная i определяется на внешнем уровне с инициализацией 1. В функции main объявлена ссылка extern на переменную i внешнего уровня. Переменная класса памяти static автоматически устанавливается в 0, так как инициализатор опущен. Вызов функции print (предполагается, что функция print определена в каком-то месте исходной программы.) печатает величины 1, 0, 0, 0.
В функции other, переменная i переопределяется как локальная переменная с начальным значением 16. Это не влияет на значение внешней переменной i. Переменная a объявляется как переменная класса памяти static с начальным значением 2. Она не противоречит переменной a, объявленной в функции main, так как видимость переменных класса памяти static на внутреннем уровне ограничена блоком, в котором она объявлена.
Значение переменной увеличивается на 2 и становится равным 4. Если бы функция other была вызвана снова в той же самой программе, то начальное значение a стало бы равным 4. Внутренние переменные класса памяти static сохраняют свои значения, когда заканчивается выполнение блока, в котором они объявлены.
Объявление функции на внешнем и внутреннем уровнях
Функции могут быть объявлены со спецификаторами класса памяти static или extern. Функции всегда имеют глобальное время жизни.
Правила видимости для функций отличаются от правил видимости для переменных. Объявления функций на внутреннем уровне имеют тот же самый смысл, что и объявления на внешнем уровне. Это значит, что функции не могут иметь блочной видимости и видимость функций не может быть вложенной. Функция объявленная как static, видима только в пределах исходного файла, в котором она определяется. Любая функция в том же самом исходном файле может вызвать функцию static, но функции static из других файлов нет. Функция static с тем же самым именем может быть объявлена в другом исходном файле.
Функции, объявленные как extern видимы в пределах всех исходных файлов, которые составляют программу. Любая функция может вызвать функцию extern.
Объявления функций, в которых опущен спецификатор класса памяти, считаются по умолчанию extern.
В объявлении переменной может быть присвоено начальное значение посредством инициализатора. Величина или величины инициализатора присваиваются переменной.
Синтаксически, записи инициализатора предшествует знак равно (=)
=<initializer>
Могут быть инициализированы переменные любого типа. Функции не инициализируются. Объявления, которые используют спецификатор класса памяти extern не могут содержать инициализатора.
Переменные, объявленные на внешнем уровне, могут быть инициализированы. Если они явно не инициализированы, то они устанавливаются в нуль во время компиляции или линкования. Любая переменная, объявленная со спецификатором класса памяти static, может быть инициализирована константным выражением. Инициализация переменных класса static выполняется один раз во время компиляции. Если отсутствует явная инициализация, то переменные класса памяти static автоматически устанавливаются в нуль.
Инициализация переменных auto и register выполняется каждый раз при входе в блок, в котором они объявлены. Если инициализатор опущен в объявлении переменной класса памяти auto или register, то начальное значение переменной не определено. Инициализация составных типов auto (массив, структура, совмещение) запрещена. Любое составное объявление класса памяти static может быть инициализировано на внешнем уровне.
Начальными значениями для внешних объявлений переменной и для всех переменных static как внешних так и внутренних должно быть константное выражение. Автоматические и регистровые переменные могут быть инициализированы константными или переменными величинами.
Базовые типы и типы указателей
Синтаксис:
=<expression>
Величина выражения присваивается переменной. Для выражения допустимы правила преобразования.
Примеры:
int x = 10; /* Example 1 */
register int *px = 0; /* Example 2 */ int c = (3 * 1024); /* Example 3 */ int *b = &x; /* Example 4 */
В первом примере x инициализируется константным выражением 10. Во втором примере, указатель px инициализирован нулем, в результате чего получился "null" указатель. В третьем примере используется константное выражение для инициализации c. В четвертом примере инициализируется указатель b адресом другой переменной x.
Синтаксис:
={<initializer-list>}
Список инициализаторов <initializer-list> - это последовательность инициализаторов, разделенных запятыми. Каждый инициализатор в последовательности- это либо константное выражение, либо список инициализаторов. Поэтому, заключенный в фигурные скобки список, может появиться внутри другого списка инициализации. Эта конструкция используется для инициализации элементов составных конструкций.
Для каждого списка инициализации значения константных выражений присваиваются в порядке следования элементов составной переменной. Когда инициализируется совмещение, то список инициализаторов представляет собой единственное константное выражение. Величина константного выражения присваивается первому элементу совмещения.
Если в списке инициализации меньше величин, чем их имеется в составном типе, то оставшиеся памяти инициализируются нулем. Если число инициализирующих величин больше чем требуется, то выдается ошибка.
Эти правила применяются к каждому вложенному списку инициализаторов, точно так же как и ко всей конструкции в целом.
Пример:
int p[4] [3] = {
{ 1, 1, 1 },
{ 2, 2, 2 }, { 3, 3, 3,}, { 4, 4, 4,},
};
В примере объявляется массив p размерности 4 строки на 3 столбца. Элементы первой строки инициализируются 1, второй строки
2 и т. д. Заметим, что списки инициализаторов третьей и четвертой строк заканчиваются запятой. Последний список инициализаторов { 4, 4, 4,} также заканчивается запятой.
Эти дополнительные запятые допускаются, но не требуются. Требуются только те запятые, которые разделяют константные выражения и списки инициализации. Если список инициализаторов не структурирован под составной объект, то его величины присваиваются в том порядке, в котором подстыкованы элементы объекта. Поэтому вышеприведенная инициализация эквивалентна следующей:
int p[4] [3] = {
1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4
};
Фигурные скобки могут также появляться вокруг индивидуальных инициализаторов в списке.
Когда инициализируются составные переменные, то нужно позаботиться о том, чтобы правильно использовать фигурные скобки и списки инициализаторов. В следующем примере иллюстрируется более детально интерпретация компилятором фигурных скобок.
typedef struct {
int n1, n2, n3;
} triplet;
triplet nlist[2] [3] = {
{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }, /* Line 1 */ { { 10,11,12}, { 13,14,15}, { 15,16,17} } /* Line 2 */