Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Программирование на языке Си

..pdf
Скачиваний:
15
Добавлен:
12.11.2023
Размер:
17.16 Mб
Скачать

262

Программирование на языке Си

курсивно (см., например, Вирт Н. Алгоритмы + структуры дан­ ных = программы - М.: Мир, 1985. - 406 с.). В тех случаях, ко­ гда вычисляемые значения определяются с помощью простых рекуррентных соотношений, гораздо эффективнее применять итеративные методы. Таким образом, определение корня мате­ матической функции, возведение в степень и вычисление фак­ ториала только иллюстрируют схемы организации рекурсивных функций, но не являются примерами эффективного применения рекурсивного подхода к вычислениям.

В следующей главе, посвященной структурам и объединени­ ям, мы рассмотрим (§6.4) динамические информационные структуры, которые включают рекурсивность в само определе­ ние обрабатываемых данных. Именно для таких данных приме­ нение рекурсивных алгоритмов не имеет конкуренции со стороны итерационных методов.

5.7. К лассы пам яти и организация програм м

Локализация объектов. До сих пор в примерах программ мы использовали в основном только два класса памяти - авто­ матическую и динамическую память. (В последнем примере из §5.6 использована глобальная переменная int counter, которая является внешней по отношению к функциям программ m ain(), giper().) Для обозначения автоматической памяти мо­ гут применяться спецификаторы классов памяти auto или register. Однако и без этих ключевых слов-спецификаторов класса памяти любой объект (например, массив или перемен­ ная), определенный внутри блока (например, внутри тела функ­ ции), воспринимается как объект автоматической памяти. Объекты автоматической памяти существуют только внутри того блока, где они определены. При выходе из блока "память, выделенная объектам типа auto или register, освобождается, т.е. объекты исчезают. При повторном входе в блок для тех же объ­ ектов выделяются новые участки памяти, содержимое которых никак не зависит от "предыстории". Говорят, что объекты авто­

Глава 5. Функции

263

матической памяти локализованы внутри того блока, где они определены, а время их существования (время "жизни") опреде­ ляется присутствием управления внутри этого блока. Другими словами, автоматическая память всегда внутренняя, т.е. к ней можно обратиться только в том блоке, где она определена.

Пример:

#include <stdio.h>

/* Переменные автоматической памяти */ void autofunc(void)

{

int К=1; printf("\tK=%d",K); K++ ;

return;

}

void main()

{

int i;

for (i=0;i<5;i++) autofunc();

}

Результат выполнения программы:

K=1

K=1

K=1

K=1

Результат выполнения этой программы очевиден, и его мож­ но было бы не приводить, если бы не существовало еще одного класса внутренней памяти - статической внутренней памяти.

Рассмотрим программу очень похожую на приведенную:

#include <stdio.h>

/* Локальные переменные статической памяти */ Void stat(void)

{

static int K=1; printf("\tK=%d",K); K++;

return;

)

void main()

264

Программирование на языке Си

int i ;

for (i=0;i<5;i++) stat() ;

)

Результат выполнения программы:

К=1

К=2

К=3

К=4

К-5

Отличие функций autofunc() и stat() состоит в наличии спе­ цификатора static при определении переменной int К, локализо­ ванной в теле функции stat(). Переменная К в функции autofunc() - это переменная автоматической памяти, она опре­ деляется и инициализируется при каждом входе в функцию. Пе­ ременная static int К получает память и инициализируется только один раз. При выходе из функции stat() последнее зна­ чение внутренней статической переменной К сохраняется до последующего вызова этой же функции. Сказанное иллюстри­ рует результат выполнения программы.

Глобальные объекты. О локализации объектов и о внутрен­ ней памяти блоков мы только что поговорили. Следует обратить внимание на возможность использовать внутри блоков объекты, которые по месторасположению своего определения оказыва­ ются глобальными по отношению к операторам и определениям блока. Напомним, что блок - это не только тело функции, но и любая последовательность определений и операторов, заклю­ ченная в фигурные скобки.

Выше в рекурсивной программе для вычисления приближен­ ного значения корня математической функции переменная counter для подсчета количества обращений к тестовой функции giper() определена как глобальная для всех функций програм­ мы. Вот еще один пример программы с глобальной переменной:

#include <stdio.h>

int N=5; /* Глобальная переменная */ void func(void)

{

printf("\tN=%d",N);

Глава 5. Функции

265

N— ; return;

)

void main()

{

int i ;

for (i=0;i<5;i++)

{

func(); N+=2 ;

}

)

Результат выполнения программы:

N=5

N=6

N=7

N=8

N=9

Переменная int N определена вне функций m ain() и func() и является глобальным объектом по отношению к каждой из них. При каждом вызове func() значение N уменьшается на 1, в ос­ новной программе - увеличивается на 2, что и определяет ре­ зультат.

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

#include <stdio.h>

int N=5; /* Глобальная переменная */ void func(void)

printf("\tN=%d",N); N--;

return;

}

void &ain()

{

int N; /* Локальная переменная */ for (N=0;N<5;N++)

func();

)

266 Программирование на языке Си

Результат выполнения программы:

N=5 N=4 N=3 N=2 N=1

Переменная int N автоматической памяти из функции m ain() никак не влияет на значение глобальной переменной int N. Это разные объекты, которым выделены разные участки памяти. Внешняя переменная N "не видна" из функции main(), и это результат определения int N внутри нее.

Динамическая память - это память, выделяемая в процессе выполнения программы. А вот на вопрос: "Глобальный или ло­ кальный объект размещен в динамической памяти?" попытаем­ ся найти правильный ответ.

После выделения динамической памяти она сохраняется до ее явного освобождения, что может быть выполнено только с помощью специальной библиотечной функции free().

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

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

указатель (на участок динамической памяти) определен как локальный объект автоматической памяти. В этом случае выделенная память будет недоступна при выходе за преде­ лы блока локализации указателя, и ее нужно освободить перед выходом из блока;

указатель определен как локальный объект статической памяти. Динамическая память, выделенная однократно в блоке, доступна через указатель при каждом повторном входе в блок. Память нужно освободить только цо оконча­ нии ее использования;

Глава 5. Функции

267

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

Проиллюстрируем второй вариант, когда объект динамиче­ ской памяти связан со статическим внутренним (лока­ лизованным) указателем:

#include <stdio.h>

#include <alloc.h> /+ Для функций malloc( ), free( ) */

void dynam(void)

{

static char *uc=NULL; /* Внутренний указатель */

/* Защита от многократного выделения памяти: */ if(uc == NULL)

{

uc=(char*)malloc(1);

*uc='A';

)

printf("\t%c",*uc);

(*uc)++;

return;

)

void main( )

{

int i ;

for (i=0; i<5; i++) dynam( );

)

Результат выполнения программы:

A B O D E

Некоторая небрежность предыдущей программы - выделен­ ный функцией malloc() участок памяти явно не освобождается функцией free().

В следующей программе указатель на динамический участок памяти - глобальный объект:

268

 

Программирование на языке Си

#include <stdio.h>

/* Для функций malloc( ),

#include <alloc.h>

char * uk=sNULL;

free( ) */

/* Глобальный указатель */

void dynaml (void)

 

{

("\t%c",

* uk);

printf

(* uk)++;

 

}

(void)

 

void main

 

{

int i ;

uk=(char*) malloc (1); *uk=’A';

for (i=0; i<5; i++)

{

dynaml( ); (*uk)++;

>

free(uk);

)

Результат выполнения программы:

A

c

E

G

I

Динамический объект создается в основной функции и свя­ зывается с указателем uk. Там же он явным присваиванием по­ лучает начальное значение 'А'. За счет глобальности указателя динамический объект доступен в обоих функциях m ain() и dynam l(). При выполнении цикла в функции m ain() и внутри функции dynaml() изменяется значение динамического объекта.

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

Глава 5. Функции

269

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

Кроме функций, в программе могут использоваться внешние_а6ъекты - переменные, указатели, массивы и т.д. Внешние объекты должны быть определены вне текста функций.

Внешние объекты могут быть доступны из многих функций программы, однако эта доступность не всегда реализуется авто­ матически - в ряде случаев нужно дополнительное вмешатель­ ство программиста. Если объект определен в начале файла с программой, то он является глобальным для всех функций, раз­ мещенных в файле, и доступен в них без всяких дополнитель­ ных предписаний. (Ограничение - если внутри функции имя глобального объекта использовано в качестве имени внутренне­ го объекта, то внешний объект становится недостижимым, т.е. "невидимым" в теле именно этой функции.) На рис. 5.3:

объект X: доступен в f11(), П 2 () как глобальный; досту­ пен как внешний в файле 2 только в тех функциях, где бу­ дет помещено описание exterq X;

объект Y: доступен как глобальный в f21() и f22(); досту­ пен как внешний в тех функциях файла 1, где будет поме­ щено описание extern Y;

объект Z: доступен как глобальный в f22() и во всех функ­ циях файла 1 и файла 2, где помещено описание extern Z.

270

Файл 1

Объект X (определение)

— функция П 1(...) —

прототип Л2 вызов Л 2 —

— функция Л2(...)

вызов Л 1

Программирование на языке Си

Объект У(определение)

— функция f21(...) —

прототип Л 1

--------вызов Л 1 прототип f22

------- вызов f22(...)

Объект 2(определение)

— функция f22(...) —

прототип Л2

— вызов Л2

Рис. 5.3. Схема программы, размещенной вдвух файлах

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

Описание внешнего объекта не есть его определение. Пом­ ните: в определении объекту всегда выделяется память, и он может быть инициализирован. Примеры определений:

Глава 5. Функции

271

double summa [5];

char D_Phil [ ]="Doctor of Philosophy"; long M=1000;

В описаниях инициализация невозможна, нельзя указать и количество элементов массивов:

extern double summa [ ]; extern char D_Phil [ ]; extern long M;

Содержательные примеры, иллюстрирующие особенности применения внешних объектов в "многофайловых" программах, приведены в главе 8.

5.8.П ар ам етр ы ф ункции m a in ()

Всоответствии с синтаксисом языка Си основная функция каждой программы может иметь такой заголовок:

int main (int argc, char *argv [ ], char *envp[ ])

Параметр argv - массив указателей на строки; argc - пара­ метр типа int, значение которого определяет размер массива argv, т.е. количество его элементов, envp - параметр-массив ука­ зателей на-, символьные строки, каждая из которых содержит описание одной из переменных среды (окружения). Под средой понимается та программа (обычно это операционная система), которая "запустила" на выполнение функцию main( ).

Назначение параметров функции m ain() - обеспечить связь выполняемой программы с операционной системой, точнее, с командной строкой, из которой запускается программа и в ко­ торую можно вносить данные и тем самым передавать испол­ няемой программе любую информацию.

Если внутри функции m ain() нет необходимости обращаться к информации из командной строки, то параметры обычно опускаются.

Соседние файлы в папке книги