В данной главе определяются термины, которые позднее используются в данном Руководстве при описании языка С, и рассматривается структура исходного текста программы на языке С. Дается обзор некоторых характеристик языка С, которые находят свое подробное описание в других главах. В главах "Объявления" и "Функции" рассматривается синтаксис и значение объявлений и определений. Предпроцессор С и прагмы рассмотрены в разделе "Директивы предпроцессора и прагмы".
Исходная программа С это набор любого числа директив, прагм, объявлений, определений и операторов. Все эти конструкции кратко рассматриваются в последующих разделах. Чтобы эти конструкции имели смысл, они все должны иметь синтаксис, соответствующий приведенному в данном руководстве, хотя и могут следовать в программе в любом порядке (в соответствии со сформулированными в данном Руководстве правилами). Однако, порядок их появления влияет на использование переменных и функций в программе. (Дополнительная информация по данному вопросу содержится в разделе "Время и сфера действия".)
"Директива" содержит инструкции для предпроцессора С по выполнению некоторых действий над текстом программы до его компиляции.
"Прагма" дает задание компилятору выполнить конкретные действия во время компиляции.
"Объявление" устанавливает связь между именем и атрибутами переменной, функции и типом. В С все переменные должны быть объявлены до своего использования.
"Объявления функций" (или "прототипы") устанавливают имя функции, ее возвращаемый тип и, возможно, ее формальные параметры. В определении функции присутствуют те же элементы, что и в ее прототипе, плюс тело функции. Если не сделать четкого объявления функции, то компилятор сам построит ее прототип на основании всей имеющейся информации при первой ссылке на функцию, является ли это ее определением или вызовом.
Объявления функций и переменных могут появляться как внутри так и вне определения функции. Любое объявление внутри определения функции считается "внутренним" (локальным). Объявление вне всех определений функций считается "внешним" (глобальным).
Определения переменных, как и их объявления, могут происходить как на внутреннем, так и на внешнем уровне. Определения функций всегда происходят на внешнем уровне.
Обратите внимание на то, что объявления типов (например, структур, об'единений и объявления typedef), которые не включают имена переменных объявленного типа, не вызывают выделения памяти.
Пример
В следующем примере показан исходный текст простой программы на С. В исходной программе определяется функция main и объявляется функция printf с прототипом. Программа использует определяющие объявления для инициализации глобальных переменных x и y. Локальные переменные z и w объявляются, но не инициализируются. Память выделяется для всех этих переменных, но только x, y, u и v имеют содержательные значения, т.к. они были инициализированы явно или неявно. Значения z и w не имеют смысла до тех пор, пока им не присваиваются значения в выполняемых операторах.
int x=1; /* определяющие объявления для */
int y=2; /* внешних переменных */
extern int printf(char *,...); /* "прототип" функции
или ее объявление */
main () /* определение функции main */
{
int z; /* определения двух инициализированных */
int w; /* локальных переменных */
static int v; /* определение переменной с глобальным
действием */
extern int u; /* ссылочное объявление внешней
переменной, которая определяется
где-то в ином месте */
z=y+x; /* выполняемые операторы */
w=y-x;
printf("z=%d w=%d,z,w);
printf("v=%d u=%d,v,u);
}
Исходную программу можно разделить на один или несколько "исходных файлов". (Например, исходный файл может содержать лишь несколько нужных программе функций.) При компилировании программы для получения общего текста нужно отдельно скомпилировать, а затем скомпоновать ее исходные файлы. Кроме того, можно использовать директиву #include для комбинирования нескольких исходных файлов в большой исходный файл до компиляции. (Информация по "включению" файлов содержится в главе "Директивы предпроцессора и прагмы".)
Исходный файл может содержать любую комбинацию полного набора директив, прагм, объявлений и определений. Нельзя разделять элементы (например, определения функций и большие структуры данных) между разными исходными файлами. Последним символом исходного файла должен быть символ новой строки.
Исходный файл не обязательно должен содержать выполняемые операторы. Например, иногда бывает полезно поместить определения переменных в один исходный файл, а их объявления - в другой файл, где они используются. Этот метод позволяет легко находить определения и вносить в них изменения. По аналогичной причине объявления констант и макросов также часто помещают в отдельные файлы, на которые при необходимости можно легко организовать ссылку.
Директивы исходного файла применяются только к этому файлу и включенным в него файлам. Более того, директива применяется только к той части файла, которая за ней следует.
Прагмы обычно воздействуют на указанную часть исходного файла. Реализация определяет заданные в прагме действия компилятора. (Руководство по компилятору подробно описывает эффект применения конкретных прагм.)
Пример
В следующем примере показана исходная программа С, состоящая из двух исходных файлов. После компиляции этих исходных файлов их можно скомпоновать и использовать, как одну программу.
Предполагается, что функции main и max находятся в разных файлах, и выполнение программы начинается с main.
/*******************************************************
исходный файл 1 - функция main
*******************************************************/
#define ONE 1
#define TWO 2
#define THREE 3
extern int max(int a, int b); /* прототип функции */
main () /* определение функции */
{
int w=ONE, x=TWO, y=THREE;
int z=0;
z=max(x,y);
w=min(z,w);
}
В исходном файле 1 (см. выше) объявляется прототип функции
max. Этот вид объявления иногда называется "ранним объявлением".
Определение функции main содержит вызов max.
Строки, которые начинаются с символа номера (#) являются директивами предпроцессора. Эти директивы информируют предпроцессор о том, что во всем исходном файле 1 нужно заменить идентификаторы ONE, TWO и THREE на указанные цифры. Однако, эти директивы никак не относятся к исходному файлу 2 (см. ниже), который будет скомпилирован отдельно и затем скомпонован с исходным файлом 1.
/*******************************************************
исходный файл 2 - функция max
*******************************************************/
int max(int a, int b) /* обратите внимание на то, что
формальные параметры включены в заголовок функции */
{
if (a>b)
return (a);
else
return (b);
}
Исходный файл 2 содержит определение функции max. Это
определение удовлетворяет вызову max в исходном файле 1. Заметим,
что определение max соответствует стандарту C.
Дополнительная информация об этой новой форме и прототипе функции
содержится в главе "Функции".
Каждая программа на С должна иметь главную функцию, которая должна иметь имя main. Функция main является стартовой точкой выполнения программы. Она обычно управляет выполнением программы, вызывая в нужный момент другие функции. Программа обычно заканчивает свое выполнение в конце функции main, хотя ее выполнение может быть прервано и в других точках программы в зависимости от условий ее выполнения.
Исходная программа обычно состоит из нескольких функций, каждая из которых выполняет одну или несколько конкретных задач. Функция main может вызывать эти функции для выполнения соответствующих им задач. Когда main вызывает другую функцию, она передает выполнение этой функции и ее выполнение начинается с выполнения первого оператора функции. Функция возвращает управление после выполнение оператора return или после достижения конца функции.
Можно объявить любую функцию, включая main, с параметрами. Когда одна функция вызывает другую, вызванная функция принимает значения своих параметров от вызвавшей ее функции. Эти значения называются "аргументами". Можно объявить формальные параметры в main так, что они могут принимать значения из внешней среды программы. (Чаще всего эти аргументы передаются из командной строки при выполнении программы.)
Традиционно первые три параметры функции main объявляются с именами argc, argv и envp. Параметр argc объявляется для хранения общего числа передаваемых в main аргументов. Параметр argv объявляется как массив указателей, каждый элемент которого указывает на строковое представление переданного в main аргумента. Параметр envp это указатель на таблицу строковых значений, которая устанавливает среду выполнения программы.
Операционная система присваивает значения параметрам argc, argv и envp, а пользователь вводит действительные аргументы в main. Операционная система, а не язык С, определяет преобразование при передаче аргументов в конкретной системе. Дополнительная информация по данному вопросу содержится в руководстве по компилятору.
При объявлении формальных параметров функции они должны быть объявлены при определении функции. Объявление и определение функций подробно рассмотрено в главах "Объявления" и "Функции".
Для того, чтобы понять, как работает программа на С, нужно сначала понять правила, которые определяют использование переменных и функций в программе. Для осознания этих правил важно понять три основные концепции: "блок", "время действия" и "сфера действия".
Блок это последовательность объявлений, определений и операторов, которая заключена в скобки. В языке С есть два типа блоков. "Составной оператор" (более подробно рассмотренный в главе "Операторы") это один тип блока. Другой тип блока это "определение функции", которое состоит из тела функции и связанного с ней "заголовка" (имя функции, возвращаемый тип и формальные параметры). Блок может содержать в себе другие блоки за тем исключением, что блок не может содержать определения функции. О блоках внутри других блоков говорят, что они "вложены" в них.
Заметим, что хотя все составные операторы заключаются в скобки, не все заключенное в скобки есть составной оператор. Например, хотя спецификации массивов, структур и перечислимых элементов могут быть заключены в скобки, они не рассматриваются в качестве составного оператора.
"Время действия" это период выполнения программы, в котором существует переменная или функция. Все функции программы существуют на всем протяжении выполнения программы.
Время действия переменной может быть внутренним (локальным) или внешним (глобальным). Элемент с локальным временем действия ("локальный элемент") хранит и определяет значение только в блоке, в котором он был определен или объявлен. Локальному элементу всегда выделяется новое место в памяти каждый раз, когда программа входит в блок, и хранимая величина теряется, когда программа выходит из блока. Если время действия переменной глобально (глобальный элемент), то он хранит и определяет значение на всем протяжении выполнения программы.
Следующие правила определяют, имеет переменная глобальное или локальное время действия:
- Переменные, которые объявлены на внутреннем уровне (в блоке) обычно имеют локальное время действия. Можно задать для переменной в блоке глобальное время действия, если указать в ее объявлении спецификатор класса хранения static. После объявления static переменная будет хранить в себе значение от одного вызова блока до другого. Однако, к ней можно будет обратиться только в данном блоке или из вложенных в него блоков. (Доступ к элементам рассматривается в следующем разделе. Информацию о спецификаторах класса хранения можно найти в главе "Объявления".)
- Переменные, которые объявлены на внешнем уровне (вне всех блоков программы) всегда имеют глобальное время действия.
"Сфера действия" элемента определяет ту часть программы, в которой на него можно сослаться по имени. Элемент доступен только в своей сфере действия, которая может быть ограничена (в порядке усиления ограничений) файлом, функцией, блоком или прототипом функции, в котором она появляется.
В С только имя метки всегда имеет сферой своего действия функцию. (Дополнительная информация по меткам и их именам содержится в главе "Операторы".) Сфера действия любого другого элемента определяется уровнем, на котором производится его объявление. Объявленный на внешнем уровне элемент имеет сферой своего действия весь файл и доступен из любого места файла. Если его объявление происходит в блоке (включая список формальных параметров в определении функции), то сфера действия элемента ограничивается данным блоком и вложенными в него блоками. Имена формальных параметров в списке параметров прототипа функции имеют сферу действия только от объявления параметра до конца объявления функции.
Примечание
Хотя элемент с глобальным временем действия и "существует" на всем протяжении выполнения исходной программы (например, внешне объявленная переменная или локальная переменная, объявленная с ключевым словом static), он может и не быть доступен из всех частей программы.
Про элемент говорят, что он "глобально доступен", если он доступен или можно сделать соответствующее объявление для доступа к нему, из всех исходных файлов, составляющих программу. (Дополнительная информация по организации доступа между исходными файлами, известной как "компоновка", содержится в главе "Объявления".)
Следующие правила определяют доступ к переменным и функциям в программе:
В Таблице 3.1 суммируются основные факторы, определяющие время и сферу действия переменных и функций. Однако, эта таблица не учитывает всех возможных вариантов. Дополнительная информация содержится в Главе "Объявления".
Таблица 3.1. Краткий обзор времени и сферы действия
Спецификатор
Уровень Элемент Класса Время Сфера
хранения жизни действия
Внешний определение static глобальное ограничено
переменной исходным файлом,
в котором
появляется
объявление extern глобальное ограничено остатком
переменной исходного файла
прототип static глобальное ограничено
функции или единственным
определение исходным файлом
прототип extern глобальное ограничено остатком
функции исходного файла
Внутренний объявление extern глобальное блок
переменной
определение static глобальное блок
переменной
определение auto или локальное блок
переменной register
Пример
Следующий пример программы иллюстрирует блоки, вложение и доступ к переменным. В примере есть четыре уровня доступа: внешний и три уровня блоков. Предположим, что функция printf определена в каком-либо другом месте программы. Значения будут выводиться на экран так, как это показано в предшествующем каждому оператору комментарии.
#include <stdio.h>
/* i определяется на внешнем уровне */
int i=1;
/* определение функции main на внешнем уровне */
main ()
{
/* печатается 1 (значение i на внешнем уровне) */
printf("%d\n", i);
/* начало первого вложенного блока */
{
/* i и j определяются на внутреннем уровне */
int i=2; j=3;
/* печатается 2 и 3 */
printf("%d\n%d\n", i, j);
/* начало втоpого вложенного блока */
{
/* пеpеопpеделение i */
int i=0;
/* печатается 0 и 3 */
printf("%d\n%d\n", i, j);
/* конец втоpого вложенного блока */
}
/* конец пеpвого вложенного блока */
}
/*печатается 1(восстановлен внешний уровень определения*/
printf("%d\n", i);
}
В программе на языке С идентификаторы используются для ссылки на многие виды различных объектов. При написании программы на С идентификаторы используются для функций, переменных, формальных параметров, компонентов об'единений и других используемых в программе объектов. С позволяет использовать один идентификатор для нескольких элементов программы, если придерживаться правил, изложенных в этом разделе. (Определение идентификатора содержится в Главе "Элементы С".)
Компилятор устанавливает "классы" для того, чтобы различать идентификаторы, использованные для различных видов элементов. Чтобы не возникало конфликта, имя каждого класса должно быть уникальным, но в нескольких классах может быть использовано идентичное имя. Это значит, что можно использовать идентификатор для нескольких разных объектов, если эти объекты располагаются в разных классах. Компилятор может идентифицировать ссылку исходя из контекста использования идентификатора в программе. Следующий список описывает виды элементов, которым можно присваивать имена в программе на С и привила по присваиванию имен:
Переменные Имена переменных и функций находятся в
и функции поименованном классе с формальными параметрами,
именами typedef перечислимыми константами. Имена
переменных и функций должны отличаться от других
имен в этом классе с той же самой сферой действия.
Однако, можно переопределять имена переменных и
функций в блоках программы, как это описано в
разделе "Время и сфера действия".
Формальные Имена формальных параметров функций группируются с
параметры именами переменных функции, поэтому они должны
быть разными. Нельзя переобъявить формальные
параметры на верхнем уровне функции. Однако, имена
формальных параметров могут быть переопределены
(т.е. использованы для указания на другие объекты)
в блоке, который вложен в тело функции.
Перечислимые Перечислимые константы находятся в том же классе,
константы что и имена переменных и функций. Это значит, что
имена перечислимых констант должны отличаться от
имен переменных и функций той же сферы действия и
от имен других перечислимых констант этой сферы
действия. Однако, аналогично именам переменных,
имена перечислимых констант могут быть
переопределены во вложенном блоке. (Вложение
рассматривается в разделе "Время и сфера
действия".)
имена typedef Имена типов, определенные с ключевым словом
typedef, находятся в одном классе с именами
переменных и функций. Т.о. имена typedef должны
отличаться от имен всех переменных и функций той
же сферы действия, также как и от имен формальных
параметров и перечислимых констант. Аналогично
именам переменных, типы typedef могут быть
переопределены в блоках программы. См. "Время и
сфера действия".
Признаки Признаки перечислимых типов, имен и об'единений
сгруппированы в отдельный класс. Имена одних
признаков должны отличаться от других в той же
самой сфере действия. Имена признаков не
конфликтуют с какими-либо другими именами.
Компоненты Компоненты каждой структуры и об'единения формируют
класс. Имя компоненты должно быть уникальным в
структуре или в об'единении, но не обязательно должно
отличаться от других использованных в программе
имен, включая имена компонентов в других
структурах и об'единениях.
Метки Метки операторов формируют отдельный класс. Каждая
метка оператора должна отличаться от всех других
меток операторов в одной функции. Метки операторов
не обязательно должны отличаться от других имен
или других имен меток в других функциях.
Пример
Три элемента с именем student в следующем примере не конфликтуют, т.к. признаки структур, компоненты структур и имена переменных находятся в трех разных классах. Контекст использования каждого элемента позволяет корректно интерпретировать каждое появление student в программе.
Например, когда student появляется после ключевого слова struct, компилятор распознает, что это признак структуры. Когда student появляется после оператора выбора компоненты (-> или .), это имя относится к компоненте структуры. В другом случае student относится к структурной переменной.
struct student {
char student[20];
int class;
int id;
} student;