Ошибки при выделении памяти




1. Бывает ситуация, при которой память не может быть выделена. В этом случае функция malloc (и calloc) возвращает NULL. Поэтому, перед выделением памяти необходимо обнулить указатель, а после выделения проверить, не равен ли он NULL. Так же ведёт себя и realloc. Когда мы используем функцию free проверять на NULL нет необходимости, так как согласно документации free(NULL) не производит никаких действий. Применительно к последнему примеру:

?

  #include <conio.h> #include <stdio.h> #include <stdlib.h> #include <string.h>   #define TERM_WORD "end" #define SIZE_INCREMENT 10   void main() { char **words; char buffer[128]; unsigned wordCounter = 0; unsigned length; unsigned size = SIZE_INCREMENT; int i;   if (!(words = (char**) malloc(size*sizeof(char*)))) { printf("Error: can't allocate memory"); getch(); exit(1); }   do { printf("%d: ", wordCounter); scanf("%127s", buffer);   if (strcmp(TERM_WORD, buffer) == 0) { break; }   length = strlen(buffer);   if (wordCounter >= size) { size += SIZE_INCREMENT; if (!(words = (char**) realloc(words, size*sizeof(char*)))) { printf("Error: can't reallocate memory"); getch(); exit(2); } }   if (!(words[wordCounter] = (char*)malloc(length + 1))) { printf("Error: can't allocate memory"); getch(); exit(3); }   strcpy(words[wordCounter], buffer); wordCounter++; } while(1);   for (i = 0; i < wordCounter; i++) { printf("%s\n", words[i]); } getch();   for (i = 0; i < wordCounter; i++) { free(words[i]); } free(words); }

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

2. Изменение указателя, который хранит адрес выделенной области памяти. Как уже упоминалось выше, в выделенной области хранятся данные об объекте - его типе и размере. При удалении free получает эту информацию. Однако, если мы изменили указатель, то удаление приведёт к ошибке, например

?

  #include <conio.h> #include <stdio.h> #include <stdlib.h>   void main() { int *p = NULL; if (!(p = (int*) malloc(100 * sizeof(int)))) { printf("Error"); exit(1); } //Изменили указатель p++; //Теперь free не может найти метаданные об объекте free(p); //На некоторых компиляторах ошибки не будет getch(); }

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

3. Использование освобождённой области. Почему это работает в си, описано выше. Эта ошибка выливается в другую – так называемые висячие указатели (dangling pointers или wild pointers). Вы удаляете объект, но при этом забываете изменить значение указателя на NULL. В итоге, он хранит адрес области памяти, которой уже нельзя воспользоваться, при этом проверить, валидная эта область или нет, у нас нет возможности.

?

  #include <conio.h> #include <stdio.h> #include <stdlib.h>   #define SIZE 10   void main() { int *p = NULL; int i;   p = (int*) malloc(SIZE * sizeof(int)); for (i = 0; i < SIZE; i++) { p[i] = i; } free(p); for (i = 0; i < SIZE; i++) { printf("%i ", p[i]); } getch(); }

Эта программа отработает и выведет мусор, или не мусор, или не выведет. Поведение не определено.

Если же мы напишем

?

  free(p); p = NULL;

то программа выкинет исключение. Это определённо лучше, чем неопределённое поведение. Если вы освобождаете память и используете указатель в дальнейшем, то обязательно обнулите его.

4. Освобождение освобождённой памяти. Пример

?

  #include <conio.h> #include <stdio.h>   void main() { int *a, *b; a = (int*) malloc(sizeof(int)); free(a); b = (int*) malloc(sizeof(int)); free(a); free(b); _getch(); }

Здесь дважды вызывается free для переменной a. При этом, переменная a продолжает хранить адрес, который может далее быть передан кому-нибудь для использования. Решение здесь такое же как и раньше - обнулить указатель явно после удаления:

?

  #include <conio.h> #include <stdio.h>   void main() { int *a, *b; a = (int*) malloc(sizeof(int)); free(a); a = NULL; b = (int*) malloc(sizeof(int)); free(a);//вызов free(NULL) ничего не делает free(b); b = NULL; _getch(); }

5. Одновременная работа с двумя указателями на одну область памяти. Пусть, например, у нас два указателя p1 и p2. Если под первый указатель была выделена память, то второй указатель может запросто скомпрометировать эту область:

?

  #include <conio.h> #include <stdio.h> #include <stdlib.h>   #define SIZE 10   void main() { int *p1 = NULL; int *p2 = NULL; size_t i;   p1 = malloc(sizeof(int) * SIZE); p2 = p1;   for (i = 0; i < SIZE; i++) { p1[i] = i; } p2 = realloc(p1, SIZE * 5000 * sizeof(int)); for (i = 0; i < SIZE; i++) { printf("%d ", p1[i]); } printf("\n"); for (i = 0; i < SIZE; i++) { printf("%d ", p2[i]); } _getch(); }

Рассмотрим код ещё раз.

?

  int *p1 = NULL; int *p2 = NULL; size_t i;   p1 = malloc(sizeof(int) * SIZE); p2 = p1;

Теперь оба указателя хранят один адрес.

?

  p2 = realloc(p1, SIZE * 5000 * sizeof(int));

А вот здесь происходит непредвиденное. Мы решили выделить под p2 новый участок памяти. realloc гарантирует сохранение контента, но вот сам указатель p1 может перестать быть валидным. Есть разные ситуации. Во-первых, вызов malloc мог выделить много памяти, часть которой не используется. После вызова ничего не поменяется и p1 продолжит оставаться валидным. Если же потребовалось перемещение объекта, то p1 может указывать на невалидный адрес (именно это с большой вероятностью и произойдёт в нашем случае). Тогда p1 выведет мусор (или же произойдёт ошибка, если p1 полезет в недоступную память), в то время как p2 выведет старое содержимое p1. В этом случае поведение не определено.

Дву указателя на одну область памяти это вообще-то не ошибка. Бывают ситуации, когда без них не обойтись. Но это очередное минное поле для программиста.



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: