Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
84
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

381

Следовательно, встроенная функция, необходимая в нескольких исходных файлах, должна быть определена в заголовочном файле. Однако спецификация inline только советкомпилятору. Будет ли функция встроенной везде или только в данном конкретном месте, зависит от множества обстоятельств. Если компилятор пренебрегает спецификацией inline, он генерирует определение функции в исполняемом файле. Если такое определение появится в данном файле больше одного раза, это будет означать ненужную трату памяти.

Большинство компиляторов выдают предупреждение в любом из следующих случаев (обычно это требует включения режима выдачи предупреждений):

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

конкретный вызов функции может не быть подставлен по месту”. Например, в оригинальной реализации С++ компании AT&T (cfront) такая подстановка невозможна для второго вызова в пределах одного и того же выражения. В такой ситуации выражение следует переписать, разделив вызовы встроенных функций.

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

Упражнение 8.3 Установите, какие из приведенных ниже инструкций являются объявлениями, а какие

(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.Локальные объекты

Объявление переменной в локальной области видимости вводит локальный объект.

Существует три вида таких объектов: автоматические, регистровые и статические,

различающиеся временем жизни и характеристиками занимаемой памяти.

С++ для начинающих

382

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

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().

С++ для начинающих

383

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

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

С++ для начинающих

384

выполнения инструкции, где он

объявлен. Вот, например, версия функции

#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(). Его инициализация выполняется только один раз когда к

#include <iostream>

extern int traceGcd(int, int);

int main() {

int rslt = traceCcd( 15, 123 );

cout << "НОД (15,123): " << rslt << endl; return 0;

этой функции обращаются впервые. В следующей программе используется traceGcd():

}

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

глубина #1 глубина #2 глубина #3 глубина #4

НОД (15,123): 3

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

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