С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 63
Текст из файла (страница 63)
Однако спецификация inline – только“совет” компилятору. Будет ли функция встроенной везде или только в данномконкретном месте, зависит от множества обстоятельств. Если компилятор пренебрегаетспецификацией inline, он генерирует определение функции в исполняемом файле. Еслитакое определение появится в данном файле больше одного раза, это будет означатьненужную трату памяти.Большинство компиляторов выдают предупреждение в любом из следующих случаев(обычно это требует включения режима выдачи предупреждений):•само определение функции не позволяет встроить ее.
Например, она слишкомсложна. В таком случае попробуйте переписать функцию или уберитеспецификацию inline и поместите определение функции в исходный файл;•конкретный вызов функции может не быть “подставлен по месту”. Например, воригинальной реализации С++ компании AT&T (cfront) такая подстановканевозможна для второго вызова в пределах одного и того же выражения. В такойситуации выражение следует переписать, разделив вызовы встроенных функций.Перед тем как употребить спецификацию inline, изучите поведение функции во времявыполнения. Убедитесь, что ее действительно можно встроить. Мы не рекомендуемобъявлять функции встроенными и помещать их определения в заголовочный файл, еслиони не могут быть таковыми по своей природе.Упражнение 8.3Установите, какие из приведенных ниже инструкций являются объявлениями, а какие –(a)(b)(c)(d)extern int ix = 1024;int iy;extern void reset( void *p ) { /* ...
*/ }extern const int *pi;определениями, и почему:(e) void print( const matrix & );Упражнение 8.4Какие из приведенных ниже объявлений и определений вы поместили бы в заголовочный(a)(b)(c)(d)int var;inline bool is_equal( const SmallInt &, const SmallInt & ){ }void putValues( int *arr, int size );const double pi = 3.1416;файл? В исходный файл? Почему?(e) extern int total = 255;8.3. Локальные объектыОбъявление переменной в локальной области видимости вводит локальный объект.Существует три вида таких объектов: автоматические, регистровые и статические,различающиеся временем жизни и характеристиками занимаемой памяти.381С++ для начинающихАвтоматический объект существует с момента активизации функции, в которой онопределен, до выхода из нее.
Регистровый объект – это автоматический объект, длякоторого поддерживается быстрое считывание и запись его значения. Локальныйстатический объект располагается в области памяти, существующей на протяжении всеговремени выполнения программы. В этом разделе мы рассмотрим свойства всех этихобъектов.8.3.1. Автоматические объектыАвтоматический объект размещается в памяти во время вызова функции, в которой онопределен. Память для него отводится из программного стека в записи активациифункции.
Говорят, что такие объекты имеют автоматическую продолжительностьхранения,илиавтоматическуюпротяженность.Неинициализированныйавтоматический объект содержит случайное, или неопределенное, значение, оставшеесяот предыдущего использования области памяти. После завершения функции ее записьактивации выталкивается из программного стека, т.е. память, ассоциированная слокальным объектом, освобождается. Время жизни такого объекта заканчивается сзавершением работы функции, и его значение теряется.Поскольку память, отведенная локальному объекту, освобождается при завершенииработы функции, адрес автоматического объекта следует использовать с осторожностью.Например, этот адрес не может быть возвращаемым значением, так как после#include "Matrix.h"Matrix* trouble( Matrix *pm ){Matrix res;// какие-то действия// результат присвоим resreturn &res; // плохо!}int main(){Matrix m1;// ...Matrix *mainResult = trouble( &m1 );// ...выполнения функции будет относиться к несуществующему объекту:}mainResult получает значение адреса автоматического объекта res.
К несчастью,память, отведенная под res, освобождается по завершении функции trouble(). Послевозврата в main() mainResult указывает на область памяти, не отведенную никакомуобъекту. (В данном примере эта область все еще может содержать правильное значение,поскольку мы не вызывали других функций после trouble() и запись ее активации,вероятно, еще не затерта.) Подобные ошибки обнаружить весьма трудно.
Дальнейшееиспользование mainResult в программе скорее всего даст неверные результаты.Передача в функцию trouble() адреса m1 автоматического объекта функции main()безопасна. Память, отведенная main(), во время вызова trouble()находится в стеке, такчто m1 остается доступной внутри trouble().382С++ для начинающихЕсли адрес автоматического объекта сохраняется в указателе, время жизни которогобольше, чем самого объекта, такой указатель называют висячим.
Работа с ним – этосерьезная ошибка, поскольку содержимое адресуемой области памяти непредсказуемо.Если комбинация бит по этому адресу оказывается в какой-то степени допустимой (неприводит к нарушению защиты памяти), то программа будет выполняться, но результатыее будут неправильными.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 (со статическойпродолжительностью хранения).
Хотя значение такого объекта сохраняется междувызовами функции, в которой он определен, видимость его имени ограничена локальнойобластью. Статический локальный объект инициализируется во время первого383С++ для начинающихвыполненияинструкции,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. Аавтоматические объекты в подобной ситуации получают случайные значения.Следующая программа иллюстрирует разницу инициализации по умолчанию дляавтоматических и статических объектов и опасность, подстерегающую программиста вслучае ее отсутствия для автоматических объектов.С++ для начинающих385#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:valuel:valuel:valuel:valuel:valuel:00014862021474798440value2:value2:value2:value2:value2:value2:749246874868756235067108864068756sum:sum:sum:sum:sum:sum:749246874868756150970-147639881268756value1 и value2 – неинициализированные автоматические объекты.
Их начальныезначения, как можно видеть из приведенной распечатки, оказываются случайными, ипотому результаты сложения непредсказуемы. Объект depth, несмотря на отсутствиеявной инициализации, гарантированно получает значение 0, и функция func()рекурсивно вызывает сама себя только дважды.8.4.