C++ для начинающих.

8.3. Локальные объекты.

Объявление переменной в локальной области видимости вводит локальный объект. Существует три вида таких объектов: автоматические, регистровые и статические, различающиеся временем жизни и характеристиками занимаемой памяти. Автоматический объект существует с момента активизации функции, в которой он определен, до выхода из нее. Регистровый объект – это автоматический объект, для которого поддерживается быстрое считывание и запись его значения. Локальный статический объект располагается в области памяти, существующей на протяжении всего времени выполнения программы. В этом разделе мы рассмотрим свойства всех этих объектов.

8.3.1. Автоматические объекты.

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

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

#include "Matrix.h".

Matrix* trouble( Matrix *pm ).

{

Matrix res;

// какие-то действия.

// результат присвоим res.

Return res; // плохо!

}

Int main().

{

Matrix m1;

// ...

Matrix *mainResult = trouble( m1 );

// ...

}

MainResult получает значение адреса автоматического объекта res. К несчастью, память, отведенная под res, освобождается по завершении функции trouble(). После возврата в main() mainResult указывает на область памяти, не отведенную никакому объекту. (В данном примере эта область все еще может содержать правильное значение, поскольку мы не вызывали других функций после trouble() и запись ее активации, вероятно, еще не затерта.) Подобные ошибки обнаружить весьма трудно. Дальнейшее использование mainResult в программе скорее всего даст неверные результаты.

Передача в функцию trouble() адреса m1 автоматического объекта функции main() безопасна. Память, отведенная main(), во время вызова trouble()находится в стеке, так что m1 остается доступной внутри trouble().

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

8.3.2. Регистровые автоматические объекты.

Автоматические объекты, интенсивно используемые в функции, можно объявить с ключевым словом register, тогда компилятор будет их загружать в машинные регистры. Если же это невозможно, объекты останутся в основной памяти. Индексы массивов и указатели, встречающиеся в циклах, – хорошие кандидаты в регистровые объекты.

For ( register int ix =0; ix sz; ++-ix ) // ...

For ( register int *p = array ; p arraySize; ++p ) // ...

Параметры также можно объявлять как регистровые переменные:

Bool find( register int *pm, int Val ) {

While ( *pm ).

If ( *pm++ == Val ) return true;

Return false;

}

Их активное использование может заметно увеличить скорость выполнения функции.

Указание ключевого слова register – только подсказка компилятору. Некоторые компиляторы игнорируют такой запрос, применяя специальные алгоритмы для определения наиболее подходящих кандидатов на размещение в свободных регистрах.

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

8.3.3. Статические локальные объекты.

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

В таком случае локальный объект необходимо объявить как static (со статической продолжительностью хранения). Хотя значение такого объекта сохраняется между вызовами функции, в которой он определен, видимость его имени ограничена локальной областью. Статический локальный объект инициализируется во время первого выполнения инструкции, где он объявлен. Вот, например, версия функции gcd(),устанавливающая глубину рекурсии с его помощью:

#include iostream.

Int traceGcd( int vl, int v2 ).

{

Static int depth = 1;

Cout "глубина #" depth++ endl;

If ( v2 == 0 ) {

Depth = 1;

Return vl;

}

Return traceGcd( v2, vl%v2 );

}

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

#include iostream.

Extern int traceGcd(int, int);

Int main() {

Int rslt = traceCcd( 15, 123 );

Cout "НОД (15,123): " rslt endl;

Return 0;

}

Результат работы программы:

Глубина #1.

Глубина #2.

Глубина #3.

Глубина #4.

НОД (15,123): 3.

Неинициализированные статические локальные объекты получают значение 0. А автоматические объекты в подобной ситуации получают случайные значения. Следующая программа иллюстрирует разницу инициализации по умолчанию для автоматических и статических объектов и опасность, подстерегающую программиста в случае ее отсутствия для автоматических объектов.

#include iostream.

Const int iterations = 2;

Void func() {

Int value1, value2; // не инициализированы.

Static int depth; // неявно инициализирован нулем.

If ( depth iterations ).

{ ++depth; func(); }.

Else depth = 0;

Cout "\nvaluel:\t" value1;

Cout "\tvalue2:\t" value2;

Cout "\tsum:\t" value1 + value2;

}

Int main() {

For ( int ix = 0; ix iterations; ++ix ) func();

Return 0;

}

Вот результат работы программы:

Valuel: 0 value2: 74924 sum: 74924.

Valuel: 0 value2: 68748 sum: 68748.

Valuel: 0 value2: 68756 sum: 68756.

Valuel: 148620 value2: 2350 sum: 150970.

Valuel: 2147479844 value2: 671088640 sum: -1476398812.

Valuel: 0 value2: 68756 sum: 68756.

Value1 и value2 – неинициализированные автоматические объекты. Их начальные значения, как можно видеть из приведенной распечатки, оказываются случайными, и потому результаты сложения непредсказуемы. Объект depth, несмотря на отсутствие явной инициализации, гарантированно получает значение 0, и функция func() рекурсивно вызывает сама себя только дважды.