Карта памяти программы на языке С
Скомпилированная программа С имеет четыре логически обособленные области памяти. Первая — это область памяти, содержащая выполнимый код программы. Во второй области хранятся глобальные переменные. Оставшиеся две области — это стек и динамически распределяемая область памяти (называется также динамической областью, динамически распределяемой областью, кучей и неупорядоченным массивом). Стек используется для хранения вспомогательных переменных во время выполнения программы. Здесь находятся адреса возврата функций, аргументы функций, локальные переменные и т.п. Текущее состояние процессора также хранится в стеке. Динамически распределяемая область памяти, или куча — это такая свободная область памяти, для получения участков памяти из которой программа вызывает функции динамического распределения памяти. На рис. 1 показано, как распределяется память во время выполнения программы. Но не следует забывать, что конкретное распределение может быть разным в зависимости от типа процессора и реализации языка.
Стек ↓ |
↑ Динамически распределяемая область памяти |
Глобальные переменные |
Код программы |
Рис 1. Распределение памяти (карта памяти) при выполнении программ, написанной на языке C
Переме́нная в императивном программировании — поименованная, либо адресуемая иным способом область памяти, адрес которой можно использовать для осуществления доступа к данным. Данные, находящиеся в переменной (то есть по данному адресу памяти), называются значением этой переменной.
Статические и динамические переменные
Адрес поименованной ячейки памяти также может определяться как на этапе компиляции, так и во время выполнения программы. По времени создания переменные бывают статическими и динамическими. Первые создаются в момент запуска программы или подпрограммы, а вторые создаются в процессе выполнения программы.
Динамическая адресация нужна только тогда, когда количество поступающих на хранение данных заранее точно не известно. Такие данные размещают в специальных динамических структурах, тип которой выбирается в соответствии со спецификой задачи и с возможностями выбранной системы программирования. Это может быть стек, куча, очередь и т. п.
Спецификаторы класса памяти
Стандарт С поддерживает четыре спецификатора класса памяти:
extern
static
register
auto
Эти спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Общая форма объявления переменных при этом такова:
спецификатор_класса_памяти тип имя переменой;
Спецификатор класса памяти в объявлении всегда должен стоять первым.
На заметку |
Стандарты С89 и С99 из соображений удобства синтаксиса утверждают, что typedef — это спецификатор класса памяти. Однако typedef не является собственно спецификатором. |
Где объявляются переменные
Объявление переменных может быть расположено в трех местах: внутри функции, в определении параметров функции и вне всех функций. Это - места объявлений соответсвенно локальных, формальных параметров функций и глобальных переменных.
Локальные переменные
Переменные, объявленные внутри функций, называются локальными переменными. В некоторых книгах по С они называютсядинамическими переменными[2]. В этой книге используется более распространенный термин локальная переменная. Локальную переменную можно использовать только внутри блока, в котором она объявлена. Иными словами, локальная переменная невидима за пределами своего блока. (Блок программы — это описания и инструкции, объединенные в одну конструкцию путем заключения их в фигурные скобки.)
Локальные переменные существуют только во время выполнения программного блока, в котором они объявлены, создаются они при входе в блок, а разрушаются — при выходе из него. Более того, переменная, объявленная в одном блоке, не имеет никакого отношения к переменной с тем же именем, объявленной в другом блоке.
Чаще всего блоком программы, в котором объявлены локальные переменные, является функция. Рассмотрим, например, следующие две функции:
void func1(void)
{ int x;
x = 10;
}
void func2(void)
{
int x;
x = -199;
}
Целая переменная х объявлена дважды: один раз в func1() и второй — в func2(). При этом переменная х в одной функции никак не связана и никак не влияет на переменную с тем же именем в другой функции. Это происходит потому, что локальная переменная видима только внутри блока, в котором она объявлена, за пределами этого блока она невидима.
В языке С есть ключевое слово auto (спецификатор класса памяти), которое можно использовать в объявлении локальной переменной. Однако так как по умолчанию предполагается, что все переменные, не являющиеся глобальными, являются динамическими, то ключевое слово auto почти никогда не используется, а поэтому в примерах в данной книге отсутствует.
Из соображений удобства и в силу устоявшейся традиции все локальные переменные функции чаще всего объявляются в самом начале функции, сразу после открывающейся фигурной скобки. Однако можно объявить локальную переменную и внутри блока программы (блок функции — это частный случай блока программы). Например:
void f(void)
{
int t;
scanf("%d%*c", &t);
if(t==1) {
char s[80]; /* эта переменная создается только
при входе в этот блок */
printf("Введите имя:");
gets(s);
/* некоторые операторы ... */
}
/* здесь переменная s невидима */
}
В этом примере локальная переменная s создается при входе в блок if и разрушается при выходе из него. Следовательно, переменная s видима только внутри блока if и не может быть использована ни в каких других местах, даже если они находятся внутри функции, содержащей этот блок.
Объявление переменных внутри блока программы помогает избежать нежелательных побочных эффектов. Переменная не существует вне блока, в котором она объявлена, следовательно, "посторонний" участок программы не сможет случайно изменить ее значение.
Если имена переменных, объявленных во внутреннем и внешнем (по отношению к нему) блоках совпадают, то переменная внутреннего блока "прячет" (т.е. скрывает, делает невидимой) переменную внешнего блока. Рассмотрим следующий пример:
#include <stdio.h>
int main(void)
{
int x;
x = 10;
if(x == 10) {
int x; /* эта x прячет внешнюю x */
x = 99;
printf("Внутренняя x: %d\n", x);
}
printf("Внешняя x: %d\n", x);
return 0;
}
Результат выполнения программы следующий:
Внутренняя х: 99
Внешняя х: 10
В этом примере переменная х, объявленная внутри блока if, делает невидимой внешнюю переменную х. Следовательно, внутренняя и внешняя х — это два разных объекта. Когда блок заканчивается, внешняя х опять становится видимой.
В стандарте С89 все локальные переменные должны быть объявлены в начале блока, до любого выполнимого оператора. Например, следующая функция вызовет ошибку компиляции в С89:
/* Эта функция вызывает ошибку компиляции
на компиляторе C89.*/
void f(void)
{
int i;
i = 10;
int j; /* ошибка в этой строке */
j = 20;
}
Однако в С99 (и в C++) эта функция вполне работоспособна, потому что в них локальная переменная может быть объявлена в любом месте внутри блока до ее первого использования.
Так как локальные переменные создаются и уничтожаются при каждом входе и выходе из блока, их значение теряется каждый раз, когда программа выходит из блока. Это необходимо учитывать при вызове функции. Локальная переменная создается при входе в функцию и разрушается при выходе из нее. Это значит, что локальная переменная не сохраняет свое значение в период между вызовами (однако можно дать указание компилятору сохранить значение локальной переменной, для этого нужно объявить ее с модификатором static).
По умолчанию локальные переменные хранятся в стеке. Стек — динамически изменяющаяся область памяти. Вот почему в общем случае локальные переменные не сохраняют свое значение в период между вызовами функций.
Локальные переменные можно инициализировать каким-либо заранее заданным значением. Это значение будет присвоено переменной каждый раз при входе в тот блок программы, в котором она объявлена. Например, следующая программа напечатает число 10 десять раз:
#include <stdio.h>
void f(void);
int main(void)
{
int i;
for(i=0; i<10; i++) f();
return 0;
}
void f(void)
{
int j = 10;
printf("%d ", j);
j++; /* этот оператор не влияет на результат */
}