Динамическое распределение памяти Способы хранения данных в памяти: 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 содержащей список директорий в которых система ищет программы для запуска