/* .................. |
*/ |
}
Объявление этой функции имеет следующий вид
#include<stdlib.h>
void* realloc(void* ptr, size_t size);
Основное назначение функции realloc() состоит в изменении размера выделенной динамической памяти. Такое использование рассматриваемой функции имеет место при условии, что ее первый параметр (ptr) не равен NULL. В том же случае, когда значение параметра ptr равно NULL, функция realloc() ведет себя подобно функции malloc().
Выполнение рассматриваемой функции в стандартах С89 и С99 несколько различаются, хотя конечный результат один и тот. Вначале рассмотрим работу этой функции в соответствии со стандартом С89. При использовании с параметром ptr, отличным от нуля, функция realloc(), делает попытку изменить размер объекта, на который установлен указатель ptr, до размера, определяемого значением параметра size. Если содержимое нового и старого объекта имеет общую часть, то содержимое общей части остается неизменным. Если размер нового объекта превышает размер старого объекта, то содержимое новой области памяти неопределенно. В соответствии со стандартом С89 значение, возвращаемое функцией, может совпадать, а может и не совпадать со значением параметра ptr. В соответствии со стандартом С99 функция realloc() должна освобождать старый блок динамической памяти и выделять новый блок. Поэтому значение, возвращаемое функцией realloc() в соответствии с этим стандартом всегда отличается от значения параметра ptr.
Приведем пример применения функции realloc(). Пусть в некоторой программе необходимо иметь массив, состоящий из “n” ‘элементов типа double. Предположим далее, что позже в зависимости от выполнения некоторых условий может потребоваться удвоение выделенной памяти для хранения элементов динамического массива. Ниже приведем фрагменты программного кода, которые решают задачу по выделению динамической
памяти для рассматриваемой задачи.
#include<stdlib.h> int main(void)
{ double* p = NULL; |
|
int |
n; |
/* |
*/ |
p = malloc(sizeof(double) * n); |
|
if(!p) |
|
{ |
puts(“Ошибка при выделении памяти”); |
|
|
||
} |
exit(1); |
|
*/ |
||
/* |
101
if(/* Проверка условия необходимости |
удвоения памяти */) |
{ |
* n); |
p = realloc(p, siztof(double) *2 |
|
if(!p) |
|
{ |
|
puts(“Ошибка при выделении памяти”); exit(1);
}
}
/* */ return 0;
}
Объявление этой функции имеет следующий вид
#include<stdlib.h>
void free(void* ptr);
Рассматриваемая функция предназначена для освобождения памяти, на которую установлен указатель ptr, Эта память должна быть предварительно выделена одной из следующих функций: calloc(), malloc() и realloc(). Если значение параметра ptr равно NULL, то функция free() никаких действий не выполняет. В противном случае, если указатель ptr не установлен на память, выделенную одной из функций, предназначенных для выделения динамической памяти, поведение функции free() не определенно. Значение указателя ptr после успешного освобождения памяти считается неопределенным, такие указатели относятся к категории недействительных указателей. Рекомендуется после освобождения памяти указателю ptr присваивать значение NULL.
Отметим, что функция free() не имеет средств, предназначенных для передачи информации о возникновении ошибки при освобождении памяти.
Рассмотрим пример применения функции free().
#include<stdlib.h> #define MAXSIZE 129 int main(void)
{
char* buf;
if(NULL == (buf = malloc(MAXSIZE)))
|
error(); |
/* |
.................. */ |
/* Строка buf теперь не нужна */
free(buf); |
|
buf = NULL; |
|
/* .................. |
*/ |
}
102
38.8.Указатели – параметры функций. Имитация
передачи по ссылке
В некоторых языках программирования, например в языке Паскаль, имеются два способа передачи параметров в функцию: передача по значению и по ссылке. В языке Си существует только один способ – передача параметров по значению. Применение указателей в языке Си открывает возможность имитировать (моделировать) передачу по ссылке. Это достигается путем установления с помощью указателя - параметра связи с памятью, выделенной в точке вызова функции. Это позволяет использовать указатели – параметры функций того, чтобы вернуть через параметры результаты вычислений, полученные в функции. Рассмотрим два примера.
Пример 1 Обмен значений двух переменных.
Постановка задачи. Даны две переменные a и b типа double. Необходимо выполнить обмен значений этих переменных. Решение необходимо оформить в виде функции.
Решение.
Прежде всего, следует убедиться в том, что попытка решить эту задачу,
используя функцию, имеющую следующий интерфейс void swap(double a, double b);
закончится неудачей. Вернуть обмен значений параметров a и b, выполненный в теле функции swap() не удается.
void swap(double *p1, double *p2)
{
double temp |
= *p1; |
*p1 |
= *p2; |
*p2 |
= temp; |
}
// Клиентский код int main(void)
{
double a = 2, b = 3; swap(&a, &b);
}
Следует убедиться в том, что при использовании рассматриваемого варианта функции swap() обмен значений переменных a и b имеет место. Этого удалось достичь за счет того, что адреса этих переменных во время вызова рассматриваемой функции оказались записанными в парметры функции. В результате в теле функции swap() с помощью разыменования указателей осуществляется работа с памятью, выделенной в точке вызова. Обратимся, например, следующей инструкции , которая находится в теле
функции swap()
*p1 = *p2;
103
Нетрудно убедиться в том, что эта инстукция работает с переменными a и b, которые находятся в клиентском коде, выполняя копирование переменной b в переменную a.
Пример 2 Обмен значений двух указателей.
Постановка задачи. Даны два указателя p1 и p1 типа double*. Решение необходимо оформиты в виде функции.
Решение.
void swap_pointers(double **p1, double **p2)
{
double* pt |
= *p1; |
*p1 |
= *p2; |
*p2 |
= *pt; |
}
// Клиентский код int main(void)
{
double a = 2; double *pa = &a; double b = 3; double *pb = &b;
swap_pointers(&pa, &pb); // ..
}
Обычно параметры функций используются для организации интерфейса по данным между контекстом точки вызова и контекстом функции. Применение указателей на функции позволяет ввести в рассмотрение новый вид параметров. Параметры, относящиеся к этому новому виду, позволяют в разрабатываемую функцию передавать другие функции. Это позволяет повысить степень универсальности разрабатываемой функции.
В качестве примера применения указателей на функцию рассмотрим решение задачи о табулировании произвольной функции одного переменного
Написать функцию пользователя, предназначенную для табулирования произвольной математической функции одного переменного. Табулирование следует выполнять в “n” равноотстоящих точках, начиная от x = хнач вполоть до x = хкон.
Программа, предназначенная для демонстрации решения поставленной задачи, состоит из трех файлов:
104
∙Файл bibl.c, который содержит определение функции table(), выполняющей табулирование, и определения табулируемых функций: sqr() и cube() .
∙Файл bibl.h, который содержит объявления используемых функций.
∙Файл main.c, который содержит тестовую программу.
/*Файл bibl.h |
|
*/ |
|
double |
sqr(double x); |
|
|
double |
cube(double |
x); |
*/ |
/* Объявления других табулируемых функций |
|||
void table(double (*pf)(double x), double |
xn, double xk, |
||
|
int |
n); |
|
/*Файл bibl.c |
|
*/ |
|
#include <stdio.h> |
|
|
|
/* |
|
|
|
Вычисление квадрата аргумента. Первая из табулируемых функций.
*/
double sqr(double x)
{ return x * x;
}
/*
Вычисление третьей степени аргумента. Вторая из табулируемых
функций
*/
double cube(double x)
{ return x * x * x;
}
/* Определения других табулируемых функций*/
/* Функция,выполняющая табулирование. Через первый параметр
этой функции передается табулируемая функция */
void table(double (*pf)(double x), double xn, double xk, int n)
{double x, dx; int i;
printf("%5s%10s%10s\n", "НОМЕР", "АРГУМЕНТ",
"ФУНКЦИЯ");
105