
- •1 Основні елементи мови с
- •1.1 Алфавіт мови програмування
- •1.2 Лексеми
- •1.3 Ключові слова
- •1.4 Ідентифікатори
- •1.5 Класифікація типів даних
- •1.6 Літерали
- •1.7 Оператори
- •1.8 Коментарі
- •1.9 Директиви препроцесора
- •1.10 Організація програми
- •2 Операції та вирази
- •2.1 Загальні відомості
- •2.2 Арифметичні операції
- •2.3 Операції приведення типів
- •2.4 Операції присвоєння
- •2.5 Операції інкремента і декремента
- •2.6 Операції порівняння
- •2.7 Операції зсуву
- •2.8 Порозрядні операції
- •2.9 Логічні операції
- •2.10 Операція sizeof
- •2.11 Операція послідовного обчислення
- •2.12 Операція умови (?:)
- •2.13 Адресні операції
- •3 Прості типи даних
- •3.1 Оголошення змінних
- •3.2 Час існування та область видимості змінних
- •3.3 Цілі типи даних
- •3.4 Дійсні типи даних
- •4 Оператори керування
- •4.1 Оператор розгалуження if
- •4.2 Оператор розгалуження if-else
- •4.3 Оператор множинного розгалуження switch
- •4.4 Оператор циклу for
- •4.5 Оператор циклу while
- •4.6 Оператор циклу do while
- •4.7 Оператор break
- •4.8 Оператор continue
- •5 Функції
- •5.1 Основні поняття
- •5.2 Види виклику функцій
- •5.3 Область видимості
- •5.4 Порожній тип void
- •5.5 Передача аргументів у функцію
- •5.6 Рекурсивні функції
- •5.7 Прототипи функцій
- •6 Покажчики
- •6.1 Визначення та ініціалізація покажчиків
- •6.2 Визначення покажчиків
- •6.3 Масиви
- •6.4 Операції порівняння
- •6.5 Копіювання рядка
- •6.6 Покажчики на функцію
- •6.7 Покажчики на void
- •6.8 Арифметика покажчиків
- •7 Масиви
- •7.1 Загальні поняття
- •7.2 Одновимірні масиви
- •7.3 Багатовимірні масиви
- •8 Рядки в с
- •8.1 Рядки
- •8.2.Створення рядків
- •8.3 Прототипи
- •8.3 Функції перетворення буферів
- •8.4 Функції перевірки літер
- •8.5 Операції з рядками
- •9 Структури, об’єднання, перерахування
- •9.1 Структури
- •9.2 Бітові поля
- •9.3 Ключове слово typedef
- •9.4 Об’єднання
- •9.5 Перераховуваний тип
- •10 Введення та виведення даних
- •10.1 Функція виведення printf
- •10.2Функція введення scanf
- •10.3 Введення та виведення у файл
- •11 Динамічне виділення пам'яті
11 Динамічне виділення пам'яті
Динамічне виділення пам'яті – спосіб виділення оперативної пам'яті комп'ютера для об'єктів у програмі, при якому виділення пам'яті під об'єкт здійснюється під час виконання програми.
При динамічному розподілі пам'яті об'єкти розміщуються в так званій «купі» (англ. heap- набір «вільних» блоків пам’яті). При конструюванні об'єкта вказується розмір запитуваної під об'єкт пам'яті, і, в разі успіху, виділена область пам'яті, умовно кажучи, «вилучається» з «купи», стаючи недоступною при подальших операціях виділення пам'яті. Протилежна за змістом операція – звільнення зайнятої раніше під який-небудь об'єкт пам'яті: звільняється пам'ять, також умовно кажучи, повертається в «купу» і стає доступною при подальших операцій виділення пам'яті.
На рисунку 11.1 представлено схему зображення «купи».
Рисунок 11.1 – Схематичне зображення «купи»
У міру створення в програмі нових об'єктів, кількість доступної пам'яті зменшується. Це, як наслідок, необхідність постійно звільняти раніше виділену пам'ять. В ідеальній ситуації програма повинна повністю звільнити всю пам'ять, яка потрібна для роботи. За аналогією з цим, кожна процедура (функція або підпрограма) повинна забезпечити звільнення всієї пам'яті, виділеної під час виконання процедури. Некоректний розподіл пам'яті приводить до так званого «витоку» ресурсів, коли виділена пам'ять не звільняється. Численні витоки пам'яті можуть призвести до вичерпання всієї оперативної пам'яті і порушити роботу операційної системи.
Інша проблема – це проблема фрагментації пам'яті. Виділення пам'яті відбувається блоками – безперервними фрагментами оперативної пам'яті (таким чином, кожен блок – це кілька байтів що йдуть підряд). У якийсь момент, в купі просто може не виявитися блоку відповідного розміру і, навіть, якщо вільної пам'яті достатньо для розміщення об'єкта, операція виділення пам'яті закінчиться невдачею.
На приклад нам потрібна пам'ять під масив розміром 6 блоків (рисунок 11.2). У нас вільних всього 8 блоків. Як відомо масив – це послідовний набір однотипних даних, тому нам потрібно послідовно 6 блоків. Як видно з рисунка, ми не можемо виділити цю пам'ять, хоча кількость блоків для цього є достатньою, тому операція закінчиться невдачею.
На рисунку 11.2 представлено схему виділення пам’яті
Рисунок 11.2 – Демонстрація виділення памяті
Для керування динамічним розподілом пам'яті використовується «збирач сміття» – програмний об'єкт, який стежить за виділенням пам'яті і забезпечує її своєчасне звільнення. Збирач сміття також стежить за тим, щоб вільні блоки мали максимальний розмір, і, при необхідності, здійснює дефрагментцію пам'яті (рисунок11.3).
На рисунку 11.3 представлено схему роботи дефрагментатора
Рисунок 11.3 – Робота дефрагментатора
Для роботи з динамічним виділенням пам’яті використовують функції malloc і calloc, за допомогою яких і відбувається виділення пам’яті, функції realloc, що дозволяє змінювати розмір виділеної ділянки пам’яті. Обовязково з цими функціями повинна використовуватись функція free, що звільняє виділену пам'ять.
Функція malloc. Прототип:
void * malloc (size_t size);
Виділяє блок розміром size байт в пам'яті, повертаючи покажчик на початок блоку. Цей покажчик завжди має тип void, і може бути приведений до необхідного типу даних при розіменовуванні. Якщо функції не вдалося виділити необхідний блок пам'яті, повертається покажчик на NULL.
Виділений блок пам'яті не ініціалізований, тобто залишається з невизначеними значеннями.
Приклад 11.1. Застосування функції malloc.
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int vari;
char * string;
int num;
printf ("Dovzhina strichky: ");
scanf ("%d", &vari);
string = (char*) malloc (vari+1);
if (string ==NULL)
exit (1);
for (num=0; num <vari; num ++)
string[num]=rand()%vari +'a';
string[vari]='\0';
printf ("Strichka: %s\n",string);
free (string);
system("PAUSE");
return 0;
}
Результат роботи програми:
Ця програма генерує рядок довжиною, яка зазначена користувачем (змінна vari), і заповнює його випадковими символами. Можлива довжина цього рядка обмежена лише кількістю вільної пам'яті в системі, яку malloc може виділити.
Функція calloc. Прототип:
void * calloc (size_t num, size_t size);
Виділяє блок пам'яті для масиву з num елементів, кожен з яких має розмір size байт, і ініціалізує всі комірки виділеної пам’яті нулями. В результаті буде виділена пам'ять розміром size*num байт.
У разі успіху повертається покажчик на блок виділеної пам'яті. Цей покажчик завжди має тип void, і може бути приведений до необхідного типу даних при розіменовуванні. Якщо функції не вдалося виділити необхідний блок пам'яті, повертається покажчик на NULL.
Приклад 11.2. Застосування функції calloc
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int vari, num;
int * p;
printf ("Kilkist' elementiv masyvu: ");
scanf ("%d",&vari);
p = (int*) calloc (vari,sizeof(int));
if (p == NULL)
exit (1);
for (num = 0;num < vari; ++num)
{
printf ("Vvedit' chyslo pid nomerom #%d: ",num);
scanf ("%d",&p[num]);
}
printf ("Vy vvely: ");
for (num = 0;num < vari; ++num)
printf ("%d ",p[num]);
free (p);
system("PAUSE");
return 0;
}
Результат роботи програми:
Слід зазначити що функція calloc потребує для свого виконання більше часу, оскільки проводить ініціалізацію виділеної пам’яті. У випадках, коли є критичною швидкодія програми, доцільно користуватися функцією malloc.
Здійснити заміну функції calloc на malloc можна переписавши стрічку :
p = (int*) calloc (vari,sizeof(int));
на
p = (int*) malloc (vari*sizeof(int));
Функція realloc.Прототип:
void * realloc (void *block, size_t size);
Змінює розмір блоку block, який раніше був виділений за допомогою функцій malloc або calloc. Аргумент size задає новий розмір блоку. Вміст блоку не змінюється.
У разі успіху повертається покажчик на блок змінений у розмірі блок пам'яті. Цей вказівник завжди має тип void, і може бути приведений до необхідного типу даних при розіменовуванні. Якщо функції не вдалося змінити розмір блоку, повертається покажчик на NULL.
Приклад 11.3. Застосування функції calloc
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *A,i;
if((A=(int*)malloc(10*sizeof(int)))==NULL)
exit(0);
for(i=0;i<10;++i)
A[i]=i;
if((A=(int*)realloc(A,33*sizeof(int)))==NULL)
exit(0);
for(;i<33;++i)
A[i]=i%3;
for(i=0;i<33;++i)
printf("%d ",A[i]);
free(A);
printf("\n");
system("PAUSE");
return 0;
}
Результат роботи програми:
Програма виділяє пам’ять для десяти елементів типу int та заповнює створений масив числами від 0 до 9. Після чого проводиться розширення масиву до розміру 33-х елементів типу int. Доповнення масиву заповнюється остачами від ділення на 3 порядкового номеру комірки масиву, при чому вміст перших десяти елементів залишається незмінним.
Функція free. Прототип:
void free (void * ptr);
Блок пам'яті, раніше виділений за допомогою виклику malloc, calloc буде звільнений, що робить його доступним для подальших дій.
Зверніть увагу, що ця функція залишає значення покажчика незмінним, отже, він як і раніше вказує на той же (в даний час недійсний) блок, а не на порожній покажчик.
В якості параметра передається покажчик на динамічно виділений блок пам'яті.
Якщо в якості аргументу передається покажчик на NULL, ніяких дій не відбувається.
Зверніть увагу на те, що дана функція нічого не повертає.
Використання функції free показане в попередніх прикладах.