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