- Оглавление
- Схема памяти программы на языке C
- RAM
- ПЗУ
- В чем разница между статически определенной переменной и динамически определенной переменной?
- Область видимости переменной
- Область видимости блока
- Область видимости файла
- Программная область видимости
- В чем разница между определением и объявлением переменной?
- Классы хранения
- auto
- static
- extern
- регистр
Оглавление
- Схема памяти программы на языке C
- ОЗУ
- ПЗУ
- В чем разница между статически определенной переменной и динамически определенной переменной?
- Область действия переменной
- Область видимости блока
- Область видимости файла
- Область видимости программы
- В чем разница между определением и объявлением переменной?
- Классы хранения
- авто
- статический
- extern
- регистр
Схема памяти программы на языке C
Прежде чем перейти к изучению области видимости и времени жизни переменной в C, нам сначала нужно определить структуру памяти обычной программы.
Как мы знаем, существует два типа памяти RAM (Random Access Memory), которая используется для хранения данных, генерируемых программой, и ROM (Read Only Memory), которая используется для хранения инструкций программы.
RAM
+---------------------+
| Memory Mapped |
| Peripherals |
+---------------------+
| Initialized |
| Data Segment |
+---------------------+
| Uninitialized |
| Data Segment |
+-----+---------------+
| | Heap |
| | |
| v |
| |
| |
| |
| |
| ^ |
| | |
| | Stack |
+-----+---------------+
- Оперативная память делится в основном на четыре части:
- Периферийные устройства, отображаемые в памяти
- Сегмент инициализированных данных: этот сегмент содержит все переменные, которые были инициализированы значением.
- Сегмент неинициализированных данных (bss)Содержит все неинициализированные переменные (те, которые были определены, но которым не присвоено значение).
- Куча
- Стек
ПЗУ
+---------------------+
| Interrupt Vector |
| Table (IVT) |
+---------------------+
| Text Segment |
| |
+---------------------+
| Constant data |
| Segment (.ro data) |
+---------------------+
| Boot loader |
| |
| |
+---------------------+
Здесь мы имеем дело с текстовым сегментом, который используется для хранения инструкции (кода) программы, и сегментом данных константы, который используется для хранения каждой статически определенной константы.
В чем разница между статически определенной переменной и динамически определенной переменной?
Переменные, которые имеют значение до начала выполнения программы, называются статически определенными переменными, а переменные, которые не имеют значения до начала выполнения программы, но получают значение во время выполнения и получают его из другого места, называются динамически определенными переменными.
Область видимости переменной
Область видимости переменной — это область, в которой мы можем использовать переменную.
В языке Си существует три различных области видимости переменных:
- Область видимости блока (область видимости функции)
- Область видимости файла
- Область видимости программы
Область видимости блока
- Переменные, имеющие такую область видимости, ограничены в своем использовании областью (блоком), в которой они были определены, мы не можем использовать их вне блока кода.
- Блок (блок кода) — это вся область, начинающаяся с
{
и заканчивающаяся}
.
Область видимости файла
- Переменные, на которые мы можем ссылаться в любом месте файла, в котором они были объявлены.
- Есть два класса хранения, которые мы рассматриваем в отношении области видимости файла,
static
иextern
.
Программная область видимости
- Это все нестатические глобальные переменные, то есть к ним можно обращаться из любой точки программы, но чтобы использовать глобальную переменную, которая была определена в файле (модуле), находящемся в другом месте от того, где мы собираемся ее использовать, мы должны сначала объявить ее.
- Время жизни глобальной переменной — это все время жизни программы.
В чем разница между определением и объявлением переменной?
- Объявление — это информирование компилятора о существовании переменной, чтобы компилятор мог игнорировать ее на данный момент, а позже компоновщик придет и свяжет значение с этой переменной, мы никогда не можем использовать переменную, которая не была сначала объявлена.
- Объявление подразумевает указание переменной символа и типа данных. Например, ключевое слово
extern int k
просто сообщает компилятору, что есть переменнаяk
и она имеет тип данныхint
, который был определен где-то в другом месте программы, используйте ее пока, а компоновщик обработает ее значение. - Только функции и константы могут быть только объявлены, а не определены и объявлены.
- Определение предоставляет компилятору всю информацию, необходимую для генерации машинного кода, когда эта сущность используется позже в программе, (у компоновщика нет работы, когда компилятор может видеть определение переменной).
- Поскольку постоянные переменные являются статическими переменными (т.е. они должны иметь значение до начала выполнения программы), определение должно быть предоставлено программистом до процесса компиляции.
- Объявление встроенного типа, такого как
int
, автоматически является определением, потому что компилятор знает, сколько места для него выделить. - Примеры:
/* Delcare and define int variables i and j. */
int i = 0;
int j;
/* Decleration without definition. */
extern int k;
int main()
{
/* This results in an error because there is no declaration instance for the variable l.
error: ld returned 1 exit status */
i = k + l;
}
Классы хранения
- Си предоставляет различные типы классов хранения, наиболее часто используемые из них:
auto
static
extern
register
Давайте возьмем все и узнаем, что они делают.
auto
- Этот класс-спецификатор используется редко, поскольку он является классом хранения по умолчанию для переменных, скопированных в блок.
- Каждый раз при входе в блок хранилище для переменных auto, определенных в этом блоке, становится доступным и перестает существовать, как только мы выходим из блока.
- Выделение и деаллокация переменной в памяти происходит автоматически, отсюда и название.
- Все неинициализированные автопеременные имеют мусорные значения, поэтому мы должны инициализировать их перед использованием.
- Пример:
/* Both these variables are similar to each other. */
int x;
auto int x;
static
- Он оказывает два различных эффекта на переменную в зависимости от ее области применения — локальной или глобальной.
- Если переменная локальная, то спецификатор
static
увеличивает время ее жизни на всю программу, но не расширяет ее область действия, ее область действия ограничена функцией, в которой она была определена. - Поскольку время жизни переменной было увеличено, ее место хранения изменилось с обычной локальной переменной, хранящейся на стеке, на постоянную переменную, хранящуюся в памяти программы.
- Пример:
void myFunc(int i)
{
int x = 2;
static int result = 0;
result += x;
printf("The result is %d on the %d iteration.n", result, i);
}
int main(void)
{
for (int i = 0; i < 4; i++)
{
myFunc(i);
}
}
- По сути, спецификатор
static
переносит значение переменной от одного вызова функции к другому. - При использовании в глобальной переменной спецификатор
static
ограничивает область действия этой переменной только тем файлом (модулем), в котором она была определена (т.е. другими словами, мы не можем использовать ключевое словоextern
для таких переменных). - Спецификатор
static
также может быть использован для функции, ограничивая ее использование так же, как и для глобальных переменных.
extern
- Используется для информирования компилятора (объявления) о том, что переменная с определенным именем и типом данных существует где-то в программе, и пока просто используйте ее как есть, а присвоением ей значения займется компоновщик.
- По сути, это используется для расширения области видимости глобальной переменной.
-
Пример, если у нас есть два файла
file1.c
иfile2.c
: -
file1.c
/*Decleration and defintion of a global variable x.*/
int x = 8;
#include "file1.c"
int main(void)
{
extern int x;
printf("The value of x %d.n", x);
return 0;
}
- Обратите внимание, что включение C-файла в другой файл является законным, но нежелательным действием. В реальной программе мы скорее определим наши переменные в заголовочном файле, а затем включим его, этот пример приведен только для иллюстрации.
регистр
- Этот спецификатор используется для хранения переменной в регистре, это делается для того, чтобы уменьшить время, необходимое программе для доступа к памяти, мы знаем, что к этой переменной будут обращаться много раз в течение жизни программы.
- Компилятор не гарантирует, что он будет хранить указанную переменную в регистре, если нет свободно доступного регистра, этот спецификатор будет проигнорирован.