Лекция 9. Завершающие темы¶
Классы хранения¶
Основные понятия¶
Классы хранения¶
При объявлении переменных и функций используются специальные ключевые слова для обозначения классов хранения.
Определение
Класс хранения - это специальное указание по определению объекту области видимости, по назначению ему определённого связывания и длительности хранения (время жизни).
Классы хранения для переменных:
- auto
- static
- register
- extern
Классы хранения для функций:
- static
- extern
Область видимости¶
Определение
Область видимости - участок кода, где разрешен доступ к объекту.
Различают следующие области видимости:
- Блочная - в пределах фигурных скобок (блока).
- Файловая - в пределах текущего файла.
- Глобальная - вся программа.
Связывание¶
Виды связывания:
- внешнее связывание;
- внутреннее связывание;
Переменная может иметь один из двух видов связывания, а может и не иметь никакого связывания.
Внешнее связывание означает, что переменные и функции после компоновки текущего файла будут доступны во всех файлах программы.
Внутреннее связывание говорит о том, что объекты будут видны только внутри текущего файла.
К локальным переменным понятие связывания не применяется.
Итак,
- Переменные с блочной областью видимости НЕ имеют связывания.
- Переменные с файловой областью видимости могут иметь либо внешнее, либо внутреннее связывание.
- Переменная с внешним связыванием может быть доступна в любом месте многофайловой программы.
- Переменная с внутренним связыванием доступна только внутри текущего файла.
Длительность хранения¶
Существуют два вида длительности хранения:
- статическая
- автоматическая
- Статическая означает, что переменная существует в процессе выполнения всей программы (глобальное время жизни). Классы хранения extern,static определяют объекты с глобальным временем жизни.
- Автоматическая означает, что они начинают существовать при входе в блок, в котором объявлены, и прекращают существование при выходе из этого блока. Классы хранения auto,register определяют объекты с локальным временем жизни.
auto¶
Класс хранения auto¶
Переменные относятся к автоматическому классу хранения если они объявлены внутри блока (внутри фигурных скобок) с ключевым словом auto или без ключевых слов и обладают следующими свойствами:
- Автоматическая длительность хранения.
- Блочная область видимости.
- Отсутствие связывания.
int main()
{
int val1;
{
int val2;
{
int val3;
...
}
...
}
...
}
Класс хранения auto¶
Переменная с классом памяти auto имеет локальное время жизни и видна только в блоке, в котором объявлена. Память для такой переменной выделяется при входе в блок и освобождается при выходе из блока. При повторном входе в блок этой переменной может быть выделен другой участок памяти.
Переменная с классом памяти auto автоматически не инициализируется. Она должна быть проинициализирована явно при объявлении путем присвоения ей начального значения. Значение неинициализированной переменной с классом памяти auto считается неопределенным.
Экранирование¶
‘Внутренняя’’ переменная экранирует ‘’внешнюю’‘, блокируя к ней доступ.
int value;
int main()
{
int value;
value=10;
...
}
int main()
{
int value;
{
int value;
value=10;
}
...
}
register¶
Класс хранения register¶
Главное назначение этого класса хранения - сообщить компилятору о желании связывания значения целочисленной переменной со свободным регистром процессора в целях ускорения доступа к ней.
С появлением этапа оптимизации при компиляции роль register уменьшается.
static¶
Класс хранения static¶
Ключевое слово static может применяться:
- к переменным с блочной видимостью;
- к переменным с файловой видимостью;
- к функциям.
Если блочная переменная объявлена как static, то она имеет:
- блочную область видимости;
- отсутствие связывания;
- статическую длительность хранения.
Значение такой переменной не теряется при выходе из блока.
Обычная локальная переменная,
теряющая значение при возвращении из функции.
int fun()
{
int value=0;
value++;
...
}
Статическая переменная,
сохраняющая значение при возвращении из функции.
int fun()
{
static int value=0;
value++;
...
}
Класс хранения texbf{static}¶
Если static применяется к файловой переменной, то она обладает следующими свойствами:
- файловой область видимости;
- внутреннее связывание;
- статическую длительность хранения.
int val1; // на переменную можно ссылаться из других файлов
static int val2; // переменная "видна" только в текущем файле
int main()
{
...
}
Класс хранения static¶
Функции, объявленные с классом static, видимы только в пределах текущего файла.
file1.cpp
static int fun1()
{
}
int fun2()
{
fun1();
}
file2.cpp
int fun1(); //Error!
int fun2(); //OK!
int main()
{
fun2(); //OK!
fun1(); //Error!
}
extern¶
Класс хранения extern¶
Переменные, объявленные вне тела функции без использования static называются внешними. Они обладают следующими свойствами:
- файловая область видимости;
- внешнее связывание;
- статическая длительность хранения.
Внешнее связывание говорит о том, что на переменную можно ссылаться из других файлов, посредством ключевого слова extern. В этом случае переменная получает глобальную область видимости.
file1.c
short flag=0;
int fun1()
{
flag=1;
}
file2.c
extern short flag;
int main()
{
if(flag)
....
}
Класс хранения extern¶
Объявление переменной со спецификатором extern информирует компилятор о том, что память для переменной выделять не требуется, так как это выполнено где-то в другом месте программы.
Объявление с extern обычно размещают в начале файла для ссылки на внешние переменные, обявленные в других файлах. Но иногда можно встретить такое объявление и внутри функции:
int a;
int fun()
{
extern int a;
}
В этом случае подразумевается ссылка на внешнюю переменную для функции fun (объявленную вне функции). Причём объявление переменной может находиться как выше fun, так и ниже по тексту.
Квалификаторы¶
volatile¶
Квалификатор volatile¶
Квалификатор volatile используется для указания переменной, которая может измениться независимо от программы (например другой программой).
Пример
В программу передаётся адрес глобальной переменной, содержащей текущее время, которое может изменяться независимо от работающей программы.
Данный квалификатор запрещает оптимизацию переменной.
Препроцессор¶
Основная задача препроцессора - производить обработку текста программы перед компиляцией.
При этом могут решаться следующие задачи:
- Включение содержимого одних файлов в другие.
- Замена одних символьных последовательностей на другие (макросы без параметров).
- Обработка макросов с параметрами.
- Условное включение кода.
Основные директивы препроцессора:
- include - включение файлов
- define - макроопределения
- undef - отмена макроопределения
- ifndef - проверка на существование макроопределения
- if,elif,else - условные директивы
- endif - завершение блока условных директив
Перед директивами в тексте программы ставится символ #.
Стандартный заголовочный файл (в специальном каталоге include)
#include <file.h>
Пользовательский заголовочный файл (в текущем каталоге)
#include "file.h"
Пример макроса без параметров:
#define N 100
char buf[N]; // char buf[100];
Ошибка при использовании define:
#define N 100;
char buf[N]; // char buf[100;]; !!!
Макрос с параметрами:
#define MUL(x,y) x*y
int val=MUL(6,8); // int val=6*8;
А в следующем примере возникнет ошибка:
int val=MUL(6+3,8-2); // int val=6+3*8-2;
Для устранения подобных ошибок надо использовать дополнительные круглые скобки
#define MUL(x,y) ((x)*(y))
int val=MUL(6+3,8-2); // ((6+3)*(8-2));
Многофайловые проекты¶
- С ростом объема и сложности программы необходимо переходить от однофайловых к многофайловым проектам.
- Это неизбежно с случае коллективной разработки.
- Разработка многофайловых программ требует особых навыков (проектирование).
- Необходимо активно использовать заголовочные файлы, препроцессор.
- Кроме ошибок компиляции могут быть ошибки компоновки.
- Трудности редактирования больших файлов.
- Затраты времени на перекомпиляцию.
- Трудности сопровождения, повторного использования кода.
- Функции группируются по назначению.
- Размеры файлов не должны быть большими.
- Данные и функции можно скрывать в модулях, используя static.
- Для ссылок на внешние функции нужно использовать прототипы (заголовки).
- Для ссылок на внешние переменные нужно использовать объявление с extern.
- Общие типы (структуры, перечисления, ..) и прототипы лучше помещать в заголовочные файлы.
В заголовочные файлы целесообразно включать:
- Прототипы функций.
- Псевдонимы, перечисления, структуры и объединения, поля битов.
- Директивы препроцессора.
- Комментарии.
В заголовочные файлы нецелесообразно включать:
- Объявления с extern
- Другие заголовочные файлы без необходимости.
- Части программы для последующей “сшивки”.
- Объявления именованных констант (const).
В заголовочные файлы нельзя включать:
- Объявления переменных.
- Полные описания функций.
- Участки исполняемого кода.
Заголовочные файлы могут включаться в исходные файлы, а также в другие заголовочные с помощью директивы препроцессора include. При этом может возникнуть ситуация, что в один и тот же исходный файл один заголовок окажется включенным несколько раз. Чтобы этого избежать, содержимое заголовка (включаемые элементы) помещаются между директивами условного включения:
#ifndef NAME_H
#define NAME_H
...
#endif
NAME_H - уникальная последовательность символов.
Вопросы для самоконтроля¶
- Что такое класс хранения?
- Какие бывают классы хранения для переменных? Для функций?
- Что такое область видимости?
- Перечислите основные области видимости.
- Что такое связывание? Какие бывают разновидности связывания?
- Что такое длительность хранения? Какие разновидности длительности хранения бывают?
- Что такое автоматический класс хранения?
- Что такое экранирование переменных и когда оно возникает?
- Когда нужно использовать класс хранения register?
- К каким переменным может применяться static?
- В чем разница между обычной (автоматической) переменной, объявленной внутри блока и static-переменной внутри блока?
- Что происходит, если static применяется к файловой переменной?
- Что можно сказать о видимости файловых переменных, объявленных с static?
- Для чего используется класс хранения extern?
- В чем заключается основная функция препроцессора?
- Какие задачи решает препроцессор?
- Перечислите основные директивы препроцессора.
- Как включить текст одного файла внутрь другого?
- Как создать макрос без параметров?
- Как создать макрос с параметрами?
- Какие ошибки могут возникнуть при использовании макросов с параметрами?
- Что отличает многофайловые проекты от однофайловых?
- С какими трудностями сталкивается разработчик больших однофайловых программ?
- Перечислите правила построения многофайловых программ.
- Что целесообразно включать в заголовочные файлы?
- Что нецелесообразно включать в заголовочные файлы?
- Что нельзя включать в заголовочные файлы?
- Как правильно организовать заголовочный файл?