Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
199
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

(a)extern int ix = 1024;

(b)int iy;

(c)extern void reset( void *p ) { /* ...

*/ }

(d)extern const int *pi;

(e)void print( const matrix & );

Упражнение 8.4 Какие из приведенных ниже объявлений и определений вы поместили бы в заголовочный

(a)int var;

(b)inline bool is_equal( const SmallInt &, const SmallInt & )

{}

(c)void putValues( int *arr, int size );

(d)const double pi = 3.1416;

файл? В исходный файл? Почему?

(e)extern int total = 255;

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 (со статической продолжительностью хранения). Хотя значение такого объекта сохраняется между вызовами функции, в которой он определен, видимость его имени ограничена локальной областью. Статический локальный объект инициализируется во время первого выполнения инструкции, где он объявлен. Вот, например, версия функции

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

gcd(),устанавливающая глубину рекурсии с его помощью:

}

Значение, ассоциированное со статическим локальным объектом 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