Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Работа с динамической памятью



Указатели чаще всего используют при работе с динамической памятью, которую иногда называют « куча » (перевод английского слова heap). Это свободная память, в которой можно во время выполнения программы выделять место в соответствии с потребностями. Доступ к выделенным участкам динамической памяти производится только через указатели. Время жизни динамических объектов – от точки создания до конца программы или до явного освобождения памяти.

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

Динамическая переменная хранится в некоторой области ОП, не обозначенной именем, и обращение к ней производится через переменную-указатель.

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

Библиотечные функции

Функции для манипулирования динамической памятью в стандарте Си следующие:

void * calloc (unsigned n, unsigned size ); – выделение памяти для размещения n объек­тов размером size байт и заполнение полученной области нулями; возвращает указатель на выделенную область памяти;

void * malloc (unsigned n ) – выделение области памяти для размещения бло­ка размером n байт; возвращает указатель на выделенную область памяти;

void * realloc (void * b, unsigned n ) – изменение размера размещенного по адресу b блока на новое значение n и копирование (при необхо­димости) содержимого блока; возвращает указатель на перераспределенную область памяти; при возникновении ошибки, например, нехватке памяти, эти функции возвращают значение NULL, что означает отсутствие адреса (нулевой адрес);

coreleft (void) – получение размера свободной памяти в байтах только для MS DOS (используется в Borland C++), тип результата: unsigned – для моделей памяти tiny, small и medium; unsigned long – для моделей памяти compact, large и huge;

void free (void * b ) – освобождение блока памяти, адресуемого указате­лем b.

Для использования этих функций требуется подключить к программе в зависимости от среды программирования заголовочный файл alloc.h или malloc.h.

__________________________________________________________________

В языке С++ введены операции захвата и освобождения памяти new и delete, рассматриваемые в разд. 16.4.

__________________________________________________________________

Пример создания одномерного динамического массива

В языке Си размерность массива при объявлении должна задаваться константным выражением.

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

Память под такие массивы выделяется с помощью функций mallос и calloc (операцией new) во время выполнения программы. Адрес начала массива хранится в переменной-указателе. Например:

int n = 10;

double *b = (double *) malloc(n * sizeof (double));

В примере значение переменной n задано, но может быть получено и программным путем.

Обнуления памяти при ее выделении не происходит. Инициализировать динамический массив при декларации нельзя.

Обращение к элементу динамического массива осуществляется так же, как и к элементу обычного – например а[3]. Можно обратиться к элементу массива и через косвенную адресацию – *(а + 3). В любом случае происходят те же действия, которые выполняются при обращении к элементу массива, декларированного обычным образом.

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

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

Пример работы с динамическим массивом:

#include < alloc.h>

void main()

{

double *x;

int n;

printf(" \nВведите размер массива – " );

scanf(" %d", & n);

if ((x = (double*)calloc(n, sizeof(*x)))==NULL) { // Захват памяти

puts(" Ошибка " );

return;

}

...

// Работа с элементами массива

...

free(x); // Освобождение памяти

}

 

__________________________________________________________________

Примеры создания одномерного и двухмерного динамических массивов с использованием операций new и delete можно посмотреть в разд. 16.4.

__________________________________________________________________

 

 

Пример создания двухмерного динамического массива

Напомним, что ID двухмерного массива – указатель на указатель. На рис. 10.1 приведена схема расположения элементов, причем в данном случае сначала выделяется память на указатели, расположенные последовательно друг за другом, а затем каждому из них выделяется соответствующий участок памяти под элементы.

...

int **m, n1, n2, i, j;

puts(" Введите размеры массива (строк, столбцов): " );

scanf(“%d%d”, & n1, & n2);

// Захват памяти для указателей – А (n1=3)

m = (int**)calloc(n1, sizeof(int*));

for (i=0; i< n1; i++) // Захват памяти для элементов – B (n2=4)

*(m+i) = (int*)calloc(n2, sizeof(int));

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

for ( j=0; j< n2; j++)

m[i] [j] = i+j; // *(*(m+i)+j) = i+j;

...

for(i=0; i< n; i++) free(m[i]); // Освобождение памяти

free(m);

...

ГЛАВА 11. Функции пользователя

 

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

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

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

Для того чтобы использовать модуль, достаточно знать только его интерфейс, а не все детали его реализации.

Разделение программы на максимально обособленные части (подпрограммы) является довольно сложной задачей, которая должна решаться на этапе проектирования программы.

В отличие от других языков программирования высокого уровня в языке Си нет разделения на подпрограммы-процедуры и подпрограммы-функции, здесь вся программа строится только из функций.

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

Функция – это именованная последовательность инструкций, выполняющая какое-либо законченное действие.

Таким образом, любая программа на языке Cи состоит из функций. Минимальная программа на Си содержит, как уже известно, единственную функцию main (основная, главная), с которой и начинается выполнение программы.

 

Декларация функции

Как и любой объект программы на языке Си, пользовательские функции необходимо декларировать. Объявление функции пользователя, т.е. ее декларация, выполняется в двух формах – в форме описания (объявления) и в форме определения, т.е. любая пользовательская функция должна быть объявлена и определена.

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

Объявление функции (прототип, заголовок) задает ее свойства – идентификатор, тип возвращаемого значения (если такое имеется), количество и типы параметров.

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

тип_результата ID_функции (список);

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

Описание прототипа дает возможность компилятору проверить соответствие типов и количества параметров при фактическом вызове этой функции.

Пример объявления функции fun, которая имеет три параметра типа int, один параметр типа double и возвращает результат типа double:

double fun(int, int, int, double);

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

Полное определение (реализация) функции имеет следующий вид:

тип_результата ID_функции(список параметров)

{

код функции

return выражение;

}

Рассмотрим составные части определения пользовательской функции.

Тип результата определяет тип выражения, значение которого возвращается в точку ее вызова при помощи оператора return выражение; (возврат). Выражение преобразуется к типу_результата, указанному в заголовке функции и передается в точку вызова. Тип возвращаемого функцией значения может быть любым базовым типом, а также указателем на массив или функцию. Если функция не должна возвращать значение, указывается тип void. В данном случае оператор return можно не ставить. Из функции, которая не описана как void, необходимо возвращать значение, используя оператор return. Если тип функции не указан, то по умолчанию устанавливается тип int.

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

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

Функция может не иметь параметров, но круглые скобки необходимы в любом случае. Если у функции отсутствует список параметров, то при декларации такой функции желательно в круглых скобках указать void. Например, void main(void){... }.

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

Пример функции, определяющей наименьшее значение из двух целочисленных переменных:

int min (int x, int y)

{

return (x< y)? x: y;

}

Функции, возвращающие значение, желательно использовать в правой части выражений языка Си, иначе возвращаемый результат будет утерян.

В языке Си каждая функция – это отдельный блок программы, вход в который возможен только через вызов данной функции.

 

Вызов функции

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

Простейший вызов функции имеет следующий формат:

ID_функции ( список аргументов );

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

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

Связь между функциями осуществляется через аргументы и возвращаемые функциями значения. Ее можно осуществить также через внешние, глобальные переменные (см. гл. 12).

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

Функции могут располагаться в исходном файле в любом порядке, при этом исходная программа может размещаться в нескольких файлах.

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

#include < stdio.h>

void f1(int);

void main(void)

{

f1(5);

}

void f1(int i)

{

int m=0;

puts(" n m p " );

while (i--) {

static int n = 0;

int p = 0;

printf(" %d %d %d \n", n++, m++, p++);

}

}

Статическая переменная n будет создана в сегменте данных ОП и проинициализируется нулем только один раз при первом выполнении оператора, содержащего ее определение, т.е. при первом вызове функции f1. Автоматическая переменная m инициализируется при каждом входе в функцию. Автоматическая переменная р инициализируется при каждом входе в блок цикла.

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

n m p

0 0 0

1 1 0

2 2 0

3 3 0

4 4 0

 


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-03-16; Просмотров: 1341; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.036 с.)
Главная | Случайная страница | Обратная связь