Динамическое распределение памяти

advertisement
Динамическое распределение памяти
Способы хранения данных в памяти:
1
Статический (static) — переменные создаются в основной
памяти программы и существуют на протяжении всего времени
выполнения.
2
Автоматический (auto) — переменные создаются при входе в
функции и разрушаются при выходе.
3
Динамический — память запрашивается во время работы
программы. Объём памяти – параметр времени выполнения
(run time parameter).
Массивы переменной длины (VLA)
(C99)
Variable length arrays (VLA)
Если в объявлении размера массива используется выражение
значение которого становится известным только во время выполнения
то это «массив переменной длины»
Пример: open “path/name” file with VLA
FILE* path_fopen(char* path, char* name, char* mode) {
char str[strlen(path) + strlen(name) + 2]; /* VLA */
strcpy(str, path);
strcat(str, "/");
strcat(str, name);
return fopen(str, mode);
}
Особенности использования
Переменную длину могут иметь только локальные массивы
Память автоматически освобождается при выходе из локального
блока
Не рекомендуется использовать для выделения значительных
объёмов памяти
Проблемы совместимости
Массивы переменной длины появились в стандарте C99
Отсутствуют в стандарте С++
Динамическое выделение памяти; стандартная
библиотека С
#include <stdlib.h> — прототипы функций
malloc(), free(), calloc() и realloc() — функции
динамического распределения памяти, которые поддерживаются
всеми трансляторами
выделяемая память находится в «свободной области»
(memory heap)
доступ к выделяемой памяти осуществляется через указатели:
динамическая память не имеет имени
Основные функции: malloc() и free()
void* malloc(size_t количество_байтов);
Вызов malloc() выделяет программе память:
количество_байтов – запрашиваемый объём памяти
функция возвращает указатель void* на первый байт
выделенной памяти или NULL если не может выполнить запрос
void free(void *ptr);
Вызов free() возвращает память операционной системе
ptr – указатель на участок памяти, выделенный ранее с
помощью функции malloc()
Пример: open “path/name” file with dynamic array
#include <stdlib.h>
FILE* PathFopen(char* path, char* name, char* mode) {
char* str = malloc(strlen (path) + strlen (name) + 2);
if(!str) {
printf("Memory could not be allocated!\n");
exit(1);
}
strcpy(str, path);
strcat(str, "/");
strcat(str, name);
FILE* ret = fopen(str, mode);
free(str);
/* releases the block of memory */
return ret;
}
Характерные ошибки
Использование памяти после free()
free(ptr);
...
*ptr = 10;
free(ptr);
/* ptr now becomes a dangling pointer */
/* ERROR: Undefined behavior */
/* ERROR: Double-free */
«Предохранитель»:
free(ptr);
ptr = NULL;
...
free(ptr);
*ptr = 10;
/* defensive style */
/* it is OK now */
/* an immediate crash */
free() используется с «неправильным» указателем
char *msg = "Default message";
int tbl[100];
int *ptr = malloc(100*sizeof(int));
...
ptr++;
free(ptr);
/* ERROR: Undefined behavior */
...
free(msg);
free(tbl);
Правила:
Сохраняйте (не меняйте) указатель возвращаемый malloc()
Применяйте free() только к указателям полученным от
функций malloc(), calloc() и realloc()
Всегда проверяйте, что память выделена успешно
char* ptr;
size_t huge = 1024*1024*1024; /* 1GB */
for(i = 0; i < 10; i++) {
ptr = malloc(huge);
printf("%i\n",ptr[1] = i);
}
Segmentation fault
Проверка:
ptr = malloc(huge);
if( !ptr ) {
printf("Memory could not be allocated! i=%d\n",i);
exit(1);
}
Memory could not be allocated! i=0
Утечка памяти (memory leaks)
int* ptr = NULL;
for(i = 0; i < 1000; i++) {
ptr = malloc(1000*sizeof(int));
....
}
free(ptr);
Используемая программой память
до цикла:
192k: PID 5964
после free(ptr): 4288k: PID 5964
Память, которую забывают освободить с помощью free(),
выходит из обращения и накапливается; это приводит к
уменьшению ресурсов всей системы.
При завершении программы, все захваченные ресурсы
возвращаются в систему
Бестиповый (универсальный) указатель void*
Назначение void*
Хранение значение указателя
Приведение к типизованному указателю
Предотвращение действий с указателем: адресная арифметика
запрещена
Пример преобразования типов для указателей
int a = 1;
double b = 2.;
int* ptr_int = &a;
double* ptr_double = &b;
void* ptr_void = NULL;
ptr_void = ptr_int;
ptr_void = ptr_double;
ptr_int = ptr_void;
ptr_double = ptr_void;
/*
/*
/*
/*
OK!
OK!
OK!
OK!
ptr_int = ptr_double;
ptr_int = (int*) ptr_double;
*/
*/
*/
*/
/* WARNING */
/* OK! */
compiler warning: assignment from incompatible pointer type
В С++ правила преобразования void* -> something* другие
Функция realloc()
void* realloc(void *ptr, size_t количество_байтов);
функция служит для изменения размера ранее выделенной
памяти (указатель ptr)
если этот указатель NULL, то функция работает так же как
malloc(количество_байтов)
после вызова realloc(ptr,...) указатель ptr становится
«висячим» (a dangling pointer)
Пример (проверки пропущены для краткости)
int* pa = malloc(100*sizeof(*pa));
double* pd = malloc(100*sizeof(*pd));
printf(" pa= %p pd= %p\n", pa, pd);
int* pc = realloc(pa,200*sizeof(*pc));
printf(" pa= %p pc= %p\n", pa, pc);
pa= 0x220a010 pd= 0x220a1b0
pa= 0x220a010 pc= 0x220a4e0
Функция calloc()
calloc(size_t число_элементов, size_t размер_элемента);
Функция «обёртка» вокруг malloc():
1
размер памяти задается двумя параметрами
2
выделенная память обнуляется
Пример
int* pa = сalloc(100,sizeof(*pa));
/* сравните: int* pa = malloc(100*sizeof(*pa)); */
if( !pa ) exit(1);
for(i = 0; i < 100; i++) pa[i] += fun(i,x,y);
...
free(pa);
Аргументы функции main(): argv и argc
int main(int argc, char *argv[])
argc – количество аргументов в командной строке; первым
аргументом считается имя программы, поэтому argc > 1
*argv[] – указатель на массив указателей; каждый элемент
которой содержит аргумент командной строки в текстовом виде
Тестовая программа печати аргументов
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("argc==%d\n", argc);
int i;
for (i=0; i<argc; i++)
printf("argv[%d] == %s\n", i, argv[i]);
return 0;
}
Назовем эту программу test
./test
argc==1
argv[0] == ./test
./test second example
argc==3
argv[0] == ./test
argv[1] == second
argv[2] == example
./test -test 1
argc==4
argv[0] == ./test
argv[1] == -test
argv[2] == 1
argv[3] == 23
23
Функции atoi() и atof()
Функции преобразования строки в числа
Для целых чисел:
int atoi(const char *str);
функции atol() и atoll() возвращают long и long long
Для чисел с плавающей точкой:
double atof(const char *str);
Строка должна содержать допустимое число.
В противном случае возвращаемое значение не определено
После числа может следовать любой символ, который не может
быть частью числа
Внимание: atoi() и atof() не проверяют ошибок
Имеются более сложные функции: strtol() и strtod()
проверяющие правильность преобразования
countdown.c
#include
#include
#include
#include
<stdlib.h>
<stdio.h>
<string.h>
<unistd.h>
/* for strcmp() */
/* for sleep() */
int main(int argc, char *argv[]) {
int disp = 0, count = 0;
if( argc<2 ) {
printf(" usage: %s number of seconds for"
" the countdown [display]\n", argv[0]);
exit(1);
}
if( argc==3 && !strcmp(argv[2], "display")) disp = 1;
for(count = atoi(argv[1]); count > 0; --count) {
if(disp) printf("%d\n", count);
sleep(1);
}
printf("\a it’s done\n"); /* sound + message */
return 0;
}
«Дополнительные» аргументы функции main()
int main(int argc, char **argv, char** envp)
*envp[] – содержит так называемые «переменные среды»
(environment variables); количество переменных определяется
по завершающему NULL
Не все операционные системы и компиляторы поддерживают
дополнительные переменные
Некоторые полезные команды UNIX
printenv – печать всех переменных среды
printf $PATH – печать переменной PATH содержащей список
директорий в которых система ищет программы для запуска
Download