Лекция 9. Завершающие темы

Классы хранения

Основные понятия

Классы хранения

При объявлении переменных и функций используются специальные ключевые слова для обозначения классов хранения.

Определение

Класс хранения - это специальное указание по определению объекту области видимости, по назначению ему определённого связывания и длительности хранения (время жизни).

Классы хранения для переменных:

  1. auto
  2. static
  3. register
  4. extern

Классы хранения для функций:

  1. static
  2. extern

Область видимости

Определение

Область видимости - участок кода, где разрешен доступ к объекту.

Различают следующие области видимости:

  1. Блочная - в пределах фигурных скобок (блока).
  2. Файловая - в пределах текущего файла.
  3. Глобальная - вся программа.

Связывание

Виды связывания:

  1. внешнее связывание;
  2. внутреннее связывание;

Переменная может иметь один из двух видов связывания, а может и не иметь никакого связывания.

Внешнее связывание означает, что переменные и функции после компоновки текущего файла будут доступны во всех файлах программы.

Внутреннее связывание говорит о том, что объекты будут видны только внутри текущего файла.

К локальным переменным понятие связывания не применяется.

Итак,

  • Переменные с блочной областью видимости НЕ имеют связывания.
  • Переменные с файловой областью видимости могут иметь либо внешнее, либо внутреннее связывание.
  • Переменная с внешним связыванием может быть доступна в любом месте многофайловой программы.
  • Переменная с внутренним связыванием доступна только внутри текущего файла.

Длительность хранения

Существуют два вида длительности хранения:

  1. статическая
  2. автоматическая
  • Статическая означает, что переменная существует в процессе выполнения всей программы (глобальное время жизни). Классы хранения extern,static определяют объекты с глобальным временем жизни.
  • Автоматическая означает, что они начинают существовать при входе в блок, в котором объявлены, и прекращают существование при выходе из этого блока. Классы хранения auto,register определяют объекты с локальным временем жизни.

auto

Класс хранения auto

Переменные относятся к автоматическому классу хранения если они объявлены внутри блока (внутри фигурных скобок) с ключевым словом auto или без ключевых слов и обладают следующими свойствами:

  1. Автоматическая длительность хранения.
  2. Блочная область видимости.
  3. Отсутствие связывания.
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, то она имеет:

  1. блочную область видимости;
  2. отсутствие связывания;
  3. статическую длительность хранения.

Значение такой переменной не теряется при выходе из блока.

Обычная локальная переменная,

теряющая значение при возвращении из функции.

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));

Многофайловые проекты

  • С ростом объема и сложности программы необходимо переходить от однофайловых к многофайловым проектам.
  • Это неизбежно с случае коллективной разработки.
  • Разработка многофайловых программ требует особых навыков (проектирование).
  • Необходимо активно использовать заголовочные файлы, препроцессор.
  • Кроме ошибок компиляции могут быть ошибки компоновки.
  1. Трудности редактирования больших файлов.
  2. Затраты времени на перекомпиляцию.
  3. Трудности сопровождения, повторного использования кода.
  • Функции группируются по назначению.
  • Размеры файлов не должны быть большими.
  • Данные и функции можно скрывать в модулях, используя static.
  • Для ссылок на внешние функции нужно использовать прототипы (заголовки).
  • Для ссылок на внешние переменные нужно использовать объявление с extern.
  • Общие типы (структуры, перечисления, ..) и прототипы лучше помещать в заголовочные файлы.

В заголовочные файлы целесообразно включать:

  • Прототипы функций.
  • Псевдонимы, перечисления, структуры и объединения, поля битов.
  • Директивы препроцессора.
  • Комментарии.

В заголовочные файлы нецелесообразно включать:

  • Объявления с extern
  • Другие заголовочные файлы без необходимости.
  • Части программы для последующей “сшивки”.
  • Объявления именованных констант (const).

В заголовочные файлы нельзя включать:

  • Объявления переменных.
  • Полные описания функций.
  • Участки исполняемого кода.

Заголовочные файлы могут включаться в исходные файлы, а также в другие заголовочные с помощью директивы препроцессора include. При этом может возникнуть ситуация, что в один и тот же исходный файл один заголовок окажется включенным несколько раз. Чтобы этого избежать, содержимое заголовка (включаемые элементы) помещаются между директивами условного включения:

#ifndef NAME_H
#define NAME_H
...
#endif

NAME_H - уникальная последовательность символов.

Вопросы для самоконтроля

  • Что такое класс хранения?
  • Какие бывают классы хранения для переменных? Для функций?
  • Что такое область видимости?
  • Перечислите основные области видимости.
  • Что такое связывание? Какие бывают разновидности связывания?
  • Что такое длительность хранения? Какие разновидности длительности хранения бывают?
  • Что такое автоматический класс хранения?
  • Что такое экранирование переменных и когда оно возникает?
  • Когда нужно использовать класс хранения register?
  • К каким переменным может применяться static?
  • В чем разница между обычной (автоматической) переменной, объявленной внутри блока и static-переменной внутри блока?
  • Что происходит, если static применяется к файловой переменной?
  • Что можно сказать о видимости файловых переменных, объявленных с static?
  • Для чего используется класс хранения extern?
  • В чем заключается основная функция препроцессора?
  • Какие задачи решает препроцессор?
  • Перечислите основные директивы препроцессора.
  • Как включить текст одного файла внутрь другого?
  • Как создать макрос без параметров?
  • Как создать макрос с параметрами?
  • Какие ошибки могут возникнуть при использовании макросов с параметрами?
  • Что отличает многофайловые проекты от однофайловых?
  • С какими трудностями сталкивается разработчик больших однофайловых программ?
  • Перечислите правила построения многофайловых программ.
  • Что целесообразно включать в заголовочные файлы?
  • Что нецелесообразно включать в заголовочные файлы?
  • Что нельзя включать в заголовочные файлы?
  • Как правильно организовать заголовочный файл?