The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа
Вперед Назад Содержание

3. Распределение памяти

Система GNU обеспечивает несколько методов для распределения пространства памяти при явном управлении программы. Они различны по общности и по эффективности.

3.1 Концепции динамического распределения памяти

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

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

Или, Вам может понадобиться блок для каждой записи или каждого определения во входных сведениях; так как Вы не можете знать заранее, сколько их будет, Вы должны определять новый блок для каждой записи или определения, поскольку Вы их читаете.

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

3.2 Динамическое Распределение в C

Язык С поддерживает два вида распределения памяти через переменные в программах C:

В GNU C, длина автоматической памяти может быть выражением, которое изменяется. В других C реализациях, это должна быть константа.

Динамическое распределение не обеспечивается C переменными; не имеется никакого класса памяти "dynamic", и не может быть переменной C, чье значение сохранено в динамически размещенном месте. Единственный способ обратиться к динамически размещенному месту - через указатель. Т.к. это менее удобно, и фактический процесс динамического распределения требует большего количества компьютерного времени, программисты использует динамическое распределение только когда ни статическое ни автоматическое распределение применить невозможно.

Например, если Вы хотите зарезервировать динамически некоторое место, чтобы разместить struct foobar, Вы не можете объявлять переменную типа struct foobar, чье содержание - динамически размещенное место. Но Вы можете объявить переменную struct foobar * типа указатель и назначить ей адрес. Тогда Вы можете использовать операторы "*" и "->" для этой переменной- указателя, чтобы обратиться по адресу:

      {
       struct foobar *ptr= (struct foobar *) malloc (sizeof (struct foobar));
       ptr->name = x;
       ptr->next = current_foobar;
       current_foobar = ptr;
       }

3.3 Беспрепятственное распределение

Наиболее общее динамическое средство распределения - malloc. Оно разрешает Вам зарезервировать блоки памяти любого размера в любое время, увеличивать или уменьшать их, и освободждать блоки индивидуально в любое время (или не освобождать) .

Базисное распределение памяти

Чтобы зарезервировать блок памяти, вызовите malloc. Прототип для этой функции находится в "stdlib.h".

      void * malloc (size _t size) (функция)
Эта функция возвращает указатель к только что размещенныому блоку size байтов длиной, или нулевому указателю если блок не мог бы быть размещен.

Содержание блока неопределено; Вы должны инициализировать его непосредственно (или использовать calloc; см. раздел 3.3.5 [Распределение очищенного места] ). Обычно Вы приводите значение указателя к виду объекта, который Вы хотите сохранять в блоке. Здесь мы показываем такой пример, и инициализации место нулями, используя библиотечную функцию memset (см. раздел 5.4 [Копирование и конкатенация]):

      struct foo *ptr;
      . . .
      ptr = (struct foo *) malloc (sizeof (struct foo));
      if (ptr == 0) abort ();
      memset (ptr, 0, sizeof (struct foo));
Вы можете сохранять результат malloc в любую переменную-указатель без приведения, потому что ANSI C автоматически преобразовывает void* в другой тип указателя когда необходимо. Но приведение необходимо в контекстах отличных от операторов назначения или если Вы хотите, чтобы ваш код выполнился в традиционном C.

Не забудьте, что при распределении пространства для строки, аргумент malloc должен быть один плюс длина строки. Это - потому что строка завершена символом \0, который не учтен в "длине" строки, но нуждается в месте. Например:

      char *ptr;
      . . .
      ptr = (char *) malloc (length + 1);

Примеры malloc

Если место не доступно, malloc возвращает нулевой указатель. Вы должны проверить значение каждого обращения к malloc. Полезно написать подпрограмму, которая вызывает malloc и сообщает ошибку, если значение ­ нулевой указатель, и возвращает результат только если значение отлично от нуля. Эта функция традиционно называется xmalloc:

      void * xmalloc (size_t size)
      {
       register void *value = malloc (size);
       if (value == 0) fatal ("virtual memory exhausted");
       return value;
      }
Это реальный пример использования malloc (через xmalloc). Функция savestring будет копировать последовательность символов в завершенную пустым указателем строку:

      char * savestring (const char *ptr, size_t len)
      {
       register char *value = (char *) xmalloc (len + 1);
       memcpy (value, ptr, len);
       value[len] = '\0';
       return value;
      }
Блок, который malloc дает Вам, уже выровнен и может содержать любой тип данных. В системе GNU, адрес всегда делится на восемь; если размер блока - 16 или больше, то адрес всегда делится на 16. Более высокая граница (типа границы страницы) необходима гораздо реже; для этих случаев, используйте memalign или valloc (см. раздел 3.3.7 [Выравниваемые блоки памяти]).

Обратите внимание, что память, размещенная после конца блока, вероятно будет в использовании для чего - нибудь еще; возможно для блока, уже размещенный другим обращением к malloc. Если Вы пытаетесь обрабатывать блок как дольше чем Вы установили, Вы можете разрушить данные, что malloc использует, чтобы следить за блоками, или Вы можете разрушить содержимое другого блока. Если Вы уже зарезервировали блок и обнаруживаете, что Вам нужен больший, используйте перераспределение (см. раздел 3.3.4 [Изменение размеров блока]).

Освобождение памяти, размещенной malloc

Когда Вы больше не нуждаетесь в блоке, который Вы создали malloc, используйте функцию free чтобы сделать блок доступным, для следующего резервировния. Прототип для этой функции находится в "stdlib.h".

      void free (void * ptr) (функция)
Функция освобождает блок памяти, указанной в ptr.

      void cfree (void * ptr) (функция)
Эта функция делает то же самое что и предыдущая. Она предусматривает совместимость снизу вверх с SunOS.

Освобождение блока изменяет содержание блока. Не ищите какие-либо данные (типа указателя на следующий блок в цепочке блоков) в блоке после его освобождения. Копируйте все необхоимое во вне блока перед его освобождением! Вот пример соответствующего способа освободить все блоки в цепочке, и строки на которые они указывают:

      struct chain {struct chain *next; char *name;}
      void free_chain (struct chain *chain)
      {
       while (chain != 0)
        {
         struct chain *next = chain->next;
         free (chain->name);
         free (chain);
         chain = next;
        }
      }
Иногда, free может фактически возвращать память в операционную систему и делать процесс меньшим. Обычно, все это позволяет, вызывая позже malloc, многократно использовать место. Тем временем, место остается в вашей программе в списе свободных, и используется внутри malloc.

В конце программы, не имеется никаких указателей на освобожденые блоки, потому что все место программы было отдано обратно системе, когда процесс завершвлся.

Изменение размера блока

Часто Вы не уверены, насколько большой блок Вам будетнужен в конечном счете. Например, блок может быть буфером, что Вы используете, чтобы содержать строку читаемую из файла; неважно какой длины Вы делаете буфер первоначально, Вы можете сталкнуться со строкой, которая будет длиннее.

Вы можете делать блок длиннее, вызывая realloc. Эта функция объявлена в "stdlib.h".

      void * realloc (void * ptr, size _t newsize) (функция)
Функция realloc изменяет размер блока с адресом ptr, на newsize.

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

Если Вы передаете пустой указатель для ptr, realloc, ведет себя точно так же как "malloc (newsize)". Это может быть удобно, но остерегайтесь более старых реализаций (до ANSI C) которые не имеют права поддерживать это поведение, и будут вероятно что-то разрушать , когда realloc получит пустой указатель.

Подобно malloc, realloc может возвращать пустой указатель, если никакое пространство памяти не доступно, чтобы увеличить блок. Когда это случается, первоначальный блок остается нетронутым; он не изменяется и не перемещается.

В большинстве случаев это не имеет значения, что случается с первоначальным блоком, когда realloc терпит неудачу, потому что прикладная программа не может продолжаться, когда не хватает памяти, и единственное что, нужно сделать - это выдать сообщение о фатальной ошибке. Часто бывает удобно написать и использовать подпрограмму, традиционно называемую xrealloc, которая заботится о сообщениях об ошибках, как xmalloc делает для malloc:

      void * xrealloc (void *ptr, size_t size)
      {
       register void *value = realloc (ptr, size);
       if (value == 0) fatal ("Virtual memory exhausted");
        return value;
      }
Вы можете также использовать realloc, чтобы уменьшить блок. Чтобы не связывать много пространства памяти, когда необходимо немного. Создание меньшего блока иногда требует копировать его, так что это может неудаться, если никакое другое место не доступно.

Если новый размер, который Вы определяете - такой же как старый, realloc, не изменит ничего и возвратит тот же самый адрес, который Вы дали.

Распределение очищенного места

Функция calloc резервирует память и обнуляет ее. Она объявлена в "stdlib.h".

      void * calloc (size_t count, size _t eltsize) (функция)
Эта функция резервирует блок достаточно длинный, чтобы содержать вектор из count элементов, каждый размера eltsize. Содержимое очищается обнулением прежде чем calloc сделает возврат.

Вы можете определять calloc следующим образом:

      void * calloc (size_t count, size_t eltsize)
      {
       size_t size = count * eltsize;
       void *value = malloc (size);
       if (value != 0) memset (value, 0, size);
       return value;
      }
Мы редко используем calloc сегодня, потому что она эквивалентна простой комбинации других средств, которые больее часто используются. Это историческое средство, которое устаревает.

Обсуждение эффективности malloc

Чтобы лучше использовать malloc, нужно знать, что GNU версия malloc всегда назначает наименьший объем памяти в блоках, чьи размеры являются степенями двойки. Она хранит раздельные пулы для каждой степени двойки. И каждый занимает столько места сколько страница. Следовательно, если у Вас есть свободный выбор размера маленького блока, чтобы сделать malloc более эффективным, делайте его степенью двойки.

Если страница разбита для определенного размеа блока, она не может многократно использоваться для другого размера, если все блоки этого размера не освобождены. В многих программах маловероятно, что это случается. Таким образом, Вы можете иногда делать программы использующие память более эффективно, используя блоки того же самого размера для многих различных целей.

Когда Вы запрашиваете о блоках памяти рамера страницы или больших, malloc использует различную стратегию; она округляет сверху размер до нескольких размеров странцы, и может обьединять и разбивать блоки как необходимо.

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

Распределение выравниваемых блоков памяти

Адрес блока, возвращенного malloc или realloc в системе GNU ­ всегда делится на восемь. Если Вы нуждаетесь в блоке, чей адрес ­ делится на более высокой степень двойки чем этот, используйте memalign или valloc. Эти функции объявлены в "stdlib.h".

С библиотекой GNU, Вы можете использовать free, чтобы освободить блоки которые возвращают memalign и valloc. Это не работает в BSD, т. к. BSD не обеспечивает ни какого способа освободить такие блоки.

      void * memalign (size _t size, size _t boundary) (функция)
Функция memalign зарезервирует блок size байтов чей адрес - делится на boundary. Граница boundary должна быть степенью двойки! Функция memalign работает, вызывая malloc, чтобы зарезервировать несколько больший блок, и возвращает адрес внутри блока, который находится на заданной границе.

      void * valloc (size _t size) (функция)
Подобна memalign с размером страницы заданным как значение второго аргумента:

      void * valloc (size_t size)
      {
       return memalign (size, getpagesize ());
      }

Проверка непротиворечивости кучи

Вы можете указать, чтобы malloc проверила непротиворечивость динамической памяти, используя функцию mcheck. Эта функция - расширение GNU, объявленное в "malloc.h".

      int mcheck (void (* abortfn) (void)) (функция)
Вызывая mcheck сообщает, чтобы malloc выполнил случайные проверки непротиворечивости. Они будут захватывать вещи типа письменного соглашения после конца блока, который был размещен malloc.

аbortfn аргумент - это функция, которая вызвается, когда несогласованность найдена. Если Вы обеспечиваете пустой указатель, используется функция аварийного прекращения работы.

Слишком поздно начинать проверку, если Вы уже зарезервировали что-­ нибудь с помощью malloc. Так как mcheck в этом случае ничего не делает. Функция возвращает -1, если Вы вызываете ее слишком поздно, и 0 иначе (в случае успеха).

Самый простой способ вызывать mcheck вовремя состоит в том, чтобы использовать опцию "-lmcheck" когда Вы компонуете вашу программу; то Вы вообще не должны изменять вашу исходную программу.

Ловушки для резервирования памяти

GNU C библиотека позволяет Вам изменять поведение malloc, realloc, и free, определяя соответствующие функции ловушки. Вы можете использовать эти ловушки, чтобы отладить программы, которые используют динамическое резервирование памяти.

Переменные-ловушки объявлены в "malloc.h".

__realloc_hook (переменная)

Значение этой переменной - указатель на функцию, которe realloc использует всякий раз, когда вызывается. Вы должны определить эту функцию, как:

      void * function (void * ptr, size _t size)
__free_hook (переменная)

Значение этой переменной - указатель на функцию, которую free использует всякий раз, когда вызывается. Вы должны определить эту функцию, как:

      void function (void * ptr)
Вы должны удостовериться, что функция, которую Вы устанавливаете как ловушку для одной из этих функций, не вызывает заменяемую функцию рекурсивно без того, чтобы сначала восстановить старое значение ловушки! Иначе, ваша программа будет эастревать в бесконечной рекурсии.
Это пример показывает как правильно использовать __malloc_hook. Мы устанавливаем функцию, которая выводит информацию каждый раз когда вызывается malloc.

      static void *(*old_malloc_hook) (size_t);
      static void * my_malloc_hook (size_t size)
      {
       void *result; __malloc_hook = old_malloc_hook;
       result = malloc (size);
       __malloc_hook = my_malloc_hook;
       printf ("malloc (%u) returns %p\n", (unsigned  int) size, result);
       return result;
      }
      main ()
      {
       ...
       old_malloc_hook = __malloc_hook;
       __malloc_hook = my_malloc_hook;
       ...
      }
Функция mcheck (см. раздел 3.3.8 [Непротиворечивость кучи]) в ходе своей работы устанавливает такие ловушки.

Статистика резервирования памяти при помощи malloc

Вы можете получить информацию относительно динамического резервирования памяти, вызывая функцию mstats. Эта функция и связанный тип данных объявлены в "malloc.h"; они являются расширением GNU.

      struct mstats (тип данных)
Этот структурный тип используется, чтобы возвратить информацию относительно динамической программы распределения памяти. Она содержит следующие поля:

      size_t bytes_total
Это - полный размер памяти, управляемой malloc, в байтах.

      size_t chunks_used
Это - число используемых кусков. (Программа распределения памяти внутренне получает куски памяти из операционной системы, и преобразует их в такие, которые удовлетворяют индивидуальным запросам malloc; см. раздел 3.3.6 [Эффективность и malloc])

      size_t bytes_used
Это число используемых байтов.

      size_t chunks_free
Это - число кусков, которые являются свободными - то есть которые были размещены операционной системой вашей программы, но теперь не используются.

      size_t bytes_free
Это - число свободных байт.

      struct mstats mstats (void) (функция)
Эта функция возвращает информацию относительно текущего динамического использования памяти в структуре типа struct mstats.

Обзор функций, имеющих отношение к функции malloc

Это обзор функций, которые имеют отношение к malloc:

      void * malloc (size _t size)
Резервирует блок из size байт. См. раздел 3.3.1 [Базисное резервирование].

      void free (void *addr)
Освобождает блок, предварительно размещенный malloc. См. раздел 3.3.3 [Освобождение после malloc].

      void * realloc (void * addr, size _t size)
Делает блок, предварительно размещенный malloc больше или меньше, возможно, копируя его по новому расположению. См. раздел 3.3.4 [Изменение размеров блока].

      void * calloc (size _t count, size _t eltsize)
Резервирует блок в count * eltsize байт, используя malloc, и обнуляет содержимое. См. раздел 3.3.5 [Распределение очищенного места].

      void * valloc (size _t size)
Зарезервирует блок в size байт, начинающийся на границе страницы. См. раздел 3.3.7 [Выравниваемые блоки памяти].

      void * memalign (size _t size, size _t boundary)
Резервирует блок в size байт, начинающийся с адреса, который является делится на выравнивание. См. раздел 3.3.7 [Выравниваемые блоки памяти].

      int mcheck (void (* abortfn) (void))
Указывает, чтобы malloc выполнил случайную проверку непротиворечивости динамически размещенной памяти, и вызыватл abortfn, если найдена несогласованность. См. раздел 3.3.8 [Непротиворечивость кучи].

      void * (* __ malloc_hook) (size _t size)
Указатель на функцию, которую malloc использует всякий раз, когда вызывается.

      void * (* __ realloc _hook) (void * ptr, size _t size)
Указатель на функцию, которую realloc использует всякий раз, когда вызывается.

      void (* __ free _hook) (void * ptr)
Указатель на функцию, которую free использует всякий раз, когда вызывается.

      struct mstats mstats (void)
Возвращают информацию относительно текущего динамического исполнения памяти. См. раздел 3.3.10 [Статистика malloc].

3.4 obstacks

obstack - пул памяти, содержащей стек объектов. Вы можете создавать любое число различных obstacks, и зарезервировать объекты в заданном obstacks. Внутри каждого obstack, последний размещенный объект должен всегда быть первым освобожденным, но различные obstacks независят от друг друга.

Кроме этого одного ограничения на порядок освобождения, obstacks полностью свободен: obstack может содержать любое число объектов любого размера. Они выполнены как макрокоманды, так что резервирование обычно очень быстро, так как объекты обычно маленькие. И единственные дополнительные непроизводительные затраты на объект необходимы, чтобы начать каждый объект с подходящей границы.

Создание obstacks

Утилиты для управления obstacks объявлены в заголовочном файле "obstack.h".

      struct obstack (тип данных)
obstack представляется структурой данных типа struct obstack. Эта структура имеет маленький фиксированный размер; она содержит состояния obstack и информацию о том, как как найти место, в котором размещены объекты. Она не содержит ни какой из объектов непосредственно. Вы не должны пробовать непосредственно обращаться к содержимому структуры; используйте только функции, описанные в этой главе.

Вы можете объявлять переменные типа struct obstack и использовать их как obstacks, или Вы можете зарезервировать obstacks динамически подобно любому другому виду объекта. Динамическое резервирование obstacks разрешает вашей программе иметь переменное число различных стеков. (Вы можете даже зарезервировать структуру obstack в другом obstack, но это редко требуется.)

Все функции, которые работают с obstacks, требуют, чтобы Вы определили, какой obstack использовать. Вы делаете это указателем типа struct obstack *. Далее, мы часто будем говорить "obstack" когда, строго говоря, имеем в виду такой указатель.

Объекты в obstack упакованы в большие блоки называемые кусками. Структура struct obstack указывает на цепочку используемых в настоящее время кусков.

Библиотека obstack получает новый кусок всякий раз, когда Вы резервируете объект, которому не удовлетворяет предыдущий кусок. Так как библиотека obstack управляет кусками автоматически, Вы не должны уделять им много внимания, но Вы должны обеспечить функцию, которую библиотека obstack должна использовать, чтобы получить кусок. Обычно Вы обеспечиваете функцию, которая использует malloc непосредственно или косвенно. Вы должны также обеспечить функцию освобождения куска. Эти вопросы описаны в следующем разделе.

Подготовка к использованию obstacks

В каждый исходный файл, в котором Вы планируете использовать функции obstack, следует включить заголовочный файл "obstack.h":

      #include <obstack.h>
Также, если исходный файл использует макрокоманду obstack_init, он должен объявить или определить две функции или макрокоманды, которые будут вызываться библиотекой obstack. Это, obstack_chunk_alloc, которая используется, чтобы зарезервировать куски памяти, в которую объекты упакованы. Другая, obstack_chunk_free, которая используется, чтобы возвратить куски, когда объекты из них освобождены.

Обычно они определены, чтобы использовать malloc через посредника xmalloc (см. раздел 3.3 [Беспрепятственное Резервирование]). Это выполнено следующей парой макроопределений:

      #define obstack_chunk_alloc xmalloc*
      #define obstack_chunk_free free
Хотя память, которую Вы получаете используя obstacks действительно, исходит из malloc, использование obstacks - быстрее, потому что malloc вызывается менее часто, для больших блоков памяти. См. раздел 3.4.10 [Куски obstack], для полной информации.

Во время выполнения, прежде, чем программа может использовать struct obstack object как obstack, она должна инициализировать obstack, вызывая obstack_init.

      void obstack_init (struct obstack * obstack_ptr) (функция)
Инициализирует obstack obstack_ptr для резервирования объектов.

Имеются два примера того, как зарезервировать пространство для obstack и инициализировать его. Первый, obstack, который является статической переменной:

      static struct obstack myobstack;
      ...
      оbstack_init (&myobstack);
Во-вторых, obstack, который самостоятельно динамически размещен:

      struct obstack * myobstack_ptr= (struct obstack *) xmalloc (sizeof(struct obstack));

      obstack_init (myobstack_ptr);

Резервирование в obstack

Наиболее прямой способ зарезервировать объект в obstack - через obstack_alloc, которая вызывается почти как malloc.

      void * obstack_alloc (struct obstack * obstack_ptr, size _t size) (функция)
Резервирует неинициализированный блок size байт в obstack и возвращает адрес. Здесь obstack_ptr определяет, в каком obstack зарезервировать блок; это - адрес struct obstack object, который представляет obstack. Каждая функция obstack или макрокоманда требует, чтобы Вы определили obstack_ptr как первый аргумент.

Например функция, которая резервирует копию строки str в определенном obstack, который находится в переменной string_obstack:

      struct obstack string_obstack;
      char * copystring (char *string)
      {
       char *s = (char *) obstack_alloc (&string_obstack, strlen (string) + 1);
       memcpy (s, string, strlen (string));
       return s;
      }
Чтобы зарезервировать блок с заданным содержимым, используйте функцию obstack_copy, объявляемую так:

      void * obstack_copy (struct obstack * obstack_ptr, void *address, size_t size) (функция)
Резервирует блок и инициализирует его, копируя size байтов данных, начинающихся по адресеу

      vоid * obstack_copy0 (struct obstack * obstack_ptr, void *address, size _t size) (функция)
Подобна obstack_copy, но конкатенирует дополнительный байт, содержащий пустой символ. Этот дополнительный байт не учтен в размере аргумента.

Функция obstack_copy удобна для копирования последовательности символов в obstack как законченной пустым символом строки. Пример использования:

      char * obstack_savestring (char * addr, size _t size)
      {
       return obstack_copy (myobstack, addr, size);
      }
Сравните это с предыдущим примером сохранения строки, использующим malloc (см. раздел 3.3.1 [Базисное резервирование]).

Освобождение объектов из obstack

Для освобждения объекта, размещенный в obstack, используйте функцию obstack_free. Так как obstack - стек объектов, освобождение одного объекта автоматически освобождает все другие объекты, зарезервированные позже в том же самом obstack.

      void obstack_free (struct obstack * obstack_ptr, void * object) (функция)
Если object - пустой указатель, все размещенные в obstack освобождаются. Если, object есть адрес объекта, размещенного в obstack, то объект освобождается, наряду с всеми размещенноми в obstack начиная с object.

Обратите внимание, что, если object является пустым указателем, то результат - неинициализированный obstack. Для освобождения всей памяти в obstack, с возможным его использованием для дальнейшего резервирования, вызовите obstack_free с адресом первого объекта, размещенного в obstack:

      obstack_free (obstack_ptr, first_object_allocated_ptr);
Обратите внимание, что объекты в obstack сгруппированы в куски. Когда все объекты в куске становятся свободными, библиотека obstack автоматически освобождает кусок (см. раздел 3.4.2 [Подготовка obstack] ). Тогда другое obstack-, или не obstack-резервирование, может многократно использовать куска, занимаемое куском.

Функции и макросы obstack

Интерфейсы для использования obstacks могут быть определены или как функции или как макрокоманды, в зависимости от транслятора. obstack средство работает со всеми C трансляторами, включая, и ANSI C и традиционный C, но имеются предосторожности, которые Вы должны соблюдать, если Вы планируете использовать трансляторы отличные от GNU C.

Если Вы используете традиционный не ANSI C транслятор, все obstack "функции" фактически определены только как макрокоманды. Вы можете вызывать эти макрокоманды подобно функциям, но Вы не можете использовать их любым другим способом (например, Вы не можете брать их адрес).

Вызов макрокоманд требует некоторых предосторожностей: а именно, первый операнд (указатель на obstack) не имеет права содержать никаких побочных эффектов, потому что он может быть вычислен больше чем один раз. Например, если Вы напишете: obstack_alloc (get_obstack (), 4), Вы увидите, что get_obstack может называться несколько раз. Если Вы используете * obstack_list_ptr ++ как аргумент-указатель obstack, Вы получите очень странные результаты, так как приращение может происходить несколько раз.

В ANSI C, каждая функция имеет, и макроопределение и определение функции. Определение функции используется, если Вы берете адрес функции без того, чтобы вызвать ее. Обычное обращение использует макроопределение по умолчанию, но Вы можете запрашивать определение функции, написав имя функции в круглых скобках, как показано здесь:

      char * x; void * (* funcp) (); /* Используем макрокоманду. */
      x = (char *) obstack_alloc (obptr, size);  /* Вызываем функцию. */
      x = (char *) (obstack_alloc) (obptr, size);  /* Берем адрес функции. */
      funcp = obstack_alloc;
Это - та же самая ситуация, что и в ANSI C для стандартных библиотечных функций. См. раздел 1.3.2 [Определение макросов].

Предупреждение: Когда Вы используете макрокоманды, Вы должны соблюдать предосторожности избавившись от побочных эффектов в первом операнде, даже в ANSI C.

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

Возрастающие объекты

Т.к. память в кусках obstack используется последовательно, возможно создать объект шаг за шагом, добавляя один или больше байт сразу к концу объекта. При таком способе, Вы не должны знать, сколько данных у Вас будет включено в объект до этого. Мы называем это методикой возрастастающих объектов. В этом разделе описаны специальные функции для добавления данных к возрастастающему объекту.

Вы не должны делать что-нибудь особенное, когда Вы начинаете наращивать объект. Использование одной из функций, чтобы добавить данные к объекту автоматически начинает его. Однако, необходимо явно указать, когда объект закончен. Это выполняется функцией obstack_finish.

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

Поскольку obstack используется для возрастастающего объекта, Вы не можете использовать obstack для обычного резервирования другого объекта. Если, Вы попробуете это сделать, то место, уже добавленное к возрастастающему объекту, станет частью другого объекта.

      void obstack_blank (struct obstack * obstack_ptr, size _t size) (функция)
Базисной функцией для добавления к возрастастающему объекту является obstack_blank, которая добавляет место без его инициализированя.

      void obstack_grow (struct obstack * obstack_ptr, void *data , size_t size) (функция)
Добавляет блок инициализированного места, используя obstack_grow, которая является аналогом obstack_copy для возрастающих объектов. Она добавляет size байт данных к возрастастающему объекту, копируя содержимое из данных.

      void obstack_grow0 (struct obstack * obstack_ptr, void *data, size_t size) (функция)
Это - аналог obstack_copy для возрастающих объектов. Она добавляет size байт копируемых из данных, добавляя дополнительный пустой символо.

      void obstack_1grow (struct obstack * obstack_ptr, char c) (функция)
Чтобы добавить только один символ, используется функция obstack_1grow. Она добавляет одиночный байт с к возрастастающему объекту.

      void * obstack_finish (struct obstack * obstack_ptr) (функция)
Когда Вы закончили увеличивать объект, используйте функцию obstack_finish, чтобы закрыть его и получить конечный адрес.

Если Вы закончили объект, obstack доступен для обычного резервирования или для увеличения другого объекта.

Когда Вы формируете объект, Вы будете вероятно должны знать, позже сколько места занял. Вы не должны следить за этим, потому что Вы можете выяснять длину из obstack перед самым окончанием объекта функцией obstack_object_size, объявленный следующим образом:

      size _t obstack_object_size (struct obstack * obstack_ptr) (функция)
Эта функция возвращает текущий размер возрастастающего объекта, в байтах. Не забудьте вызывать эту функцию перед окончанием объекта. После того, как он закончен, obstack_object_size будет возвращать нуль.

Если Вы начали увеличивать объект и желаете отменить это, Вы должны закончить его и тогда освободить его, примерно так:

      obstack_free (obstack_ptr, obstack_finish (obstack_ptr));
Это не имеет никакого эффекта, если никакой объект не возрастастал.

Вы можете использовать obstack_blank с отрицательным аргументом size, чтобы делать текущий объект меньше. Только не пробуйте сокращать его до меньше нуля, не имеется никаких сведенний, что случиться, если Вы сделаете это.

Сверхбыстро возрастастающие объекты

Обычные функции для возрастастающих объектов делают непроизводительные затраты для проверки, имеется ли участок памяти для нового роста в текущем куске. Если Вы часто создаете объекты в с маленькими шагами роста, эти непроизводительные затраты, могут быть значительны.

Вы можете уменьшать непроизводительные затраты, используя специальный "быстрый рост", т. е. функции, которые выращивают объект без проверки. Чтобы иметь здоровую программу, Вы должны делать проверку самостоятельно. Если Вы делаете это самым простым способом каждый раз. когда Вы собираетесь, добавлять данные к объекту, Вы ничего не приобретете, потому что это и делают обычные функции возрастания. Но если Вы можете проверить менее часто, или проверять более эффективно, то Вы сделаете программу быстрее.

Функция obstack_room возвращает количество памяти, доступной в текущем куске. Она объявлена следующим образом:

      size _t obstack_room (struct obstack * obstack_ptr) (функция)
Возвращает число байтов которые могут быть добавлены к текущему возрастастающему объекту (или к объекту, собирающемуся начаться) в obstack при использовании быстрых функций роста.

Если Вы знаете, что имеется участок памяти, Вы можете использовать эти быстрые функции роста для добавления данных к возрастастающему объекту:

      void obstack_1grow_fast (struct obstack * obstack_ptr, char c) (функция)
Функция obstack_1grow_fast добавляет один байт, содержащий символ с к возрастастающему объекту в obstack obstack_ptr.

      void obstack_blank_fast (struct obstack * obstack_ptr, size _t size) (функция)
Функция obstack_blank_fast добавляет size байтов к возрастастающему объекту в obstack obstack_ptr без их инициализации.

Если Вы проверяете место, используя obstack_room и нет достаточного участка памяти, для того, что Вы хотите добавлять, то быстрые функции роста не безопасны. В этом случае, просто используйте соответствующую обычную функцию роста. Если она будет копировать объект в новый кусок; то будет иметься больше доступной памяти.

Так, каждый раз когда Вы используете обычную функцию роста, проверяйте есть ли достаточного места, используя obstack_room. Как только объект скопирован в новый кусок, будет снова иметься много места, так что программа будет начинать использовать быстрые функции роста.

Вот пример:

      void add_string (struct obstack * obstack, char * ptr, size _t len)
      {
       while  (len > 0)
       {
        if (obstack_room (obstack) > len)
         {          /* Мы имеем достаточный участок памяти: добавляйте все
                        быстро. */
           while (len - > 0) obstack_1grow_fast (obstack, * ptr ++);
         }
        else
         {
      /* Нет достаточного участка памяти. Добавьте один символ, он может
 быть скопирован в новый кусок для создания места. */
          obstack_1grow (obstack, * ptr ++); len -;
         }
       }
      }

Состояние obstack

Имеются функции, которые обеспечивают информацию относительно текущего состояния резервирования в obstack. Вы можете использовать их, чтобы узнать относительно объекта при все еще возрастании это.

      void * obstack_base (struct obstack * obstack_ptr) (функция)
Эта функция возвращает временный адрес начала в настоящее время возрастастающего объекта в obstack_ptr. Если Вы заканчите объект немедленно, он будет иметь этот адрес. Если Вы сначала его увеличиваете, он может перерасти этот кусок и адрес будет изменяться!

Если никакой объект в данный момент не возрастастает, это значение показывает, где будет начинаться следующий объект, который Вы зарезервируете.

      void * obstack_next_free (struct obstack * obstack_ptr) (функция)
Эта функция возвращает адрес первого свободного байта в текущем куске obstack obstack_ptr, т. е. - конца в настоящее время возрастастающего объекта. Если никакой объект не возрастастает, obstack_next_free, возвращает то же самое значение что и obstack_base.

      size _t obstack_object_size (struct obstack * obstack_ptr) (функция)
Эта функция возвращает размер в байтах возрастастающего в настоящее время объекта. Она эквивалентна toobstack_next_free (obstack_ptr) ­ obstack_base (obstack_ptr)

Выравнивание данных в obstacks

Каждый obstack имеет границу выравнивания; каждый объект, размещенный в obstack автоматически начинается на адресе, который делится на заданную границу (выравнивание). По умолчанию, эта граница ­ 4 байта.

Чтобы обращаться к границе выравнивания obstack, используйте макрокоманду obstack_alignment_mask, чей функциональлный прототип

      int obstack_alignment_mask (struct obstack * obstack_ptr) (макрос)
Его значение - битовая маска; установленная еденица, указывает, что соответствующий бит в адресе объекта должен быть 0. Значение маски должно быть на еденицу меньше чем степень 2; и все адреса объекта ­ делится на эту степень 2. По умолчанию значение маски - 3, чтобы адреса - делились на 4. Значение маски 0 означает, что объект может начинаться с любого целого адреса (то-есть никакое выравнивание не требуется).

Расширение макрокоманды obstack_alignment_mask - именующее выражение, так что Вы можете изменять маску назначением. Например, это утверждение:

      obstack_alignment_mask (obstack_ptr) = 0;
Выключает обработку выравнивания в заданном obstack.

Обратите внимание, что изменение в маске выравнивания не действует до окончания или размещения следующего объекта в obstack. Если Вы не увеличиваете объект, Вы может заставить новую маску выравнивания воздействовать немедленно, вызывая obstack_finish. Она заканчит объект нулевой длины и то сделает соответствующее выравнивание для следующего объекта.

Куски obstack

obstacks работает, резервируя пространство для себя в больших кусках, и разбивает место снаружи на куски, чтобы удовлетворить ваши запросы. Куски - обычно длиной 4096 байтов, если Вы не определите другой размер куска. Размер куска включает 8 байтов непроизводительных затрат, которые фактически не используются для сохранения объектов. Независимо от заданного размера, длинные объекты будут размещены в более длинные куски, когда необходимо.

obstack библиотека зервирует куски, вызывая функцию obstack_chunk_alloc, которую Вы должны определить. Когда кусок больше не нужен, если Вы освободили в нем все объекты, obstack библиотека освобождает кусок, вызывая obstack_chunk_free, которыую Вы должны также определить.

Эти две функции должны быть определены (как макрокоманды) или объявляться (как функции) в каждом исходном файле, который использует obstack_init (см. раздел 3.4.1 [Создание obstacks]). Наиболее часто они определены как макрокоманды подобно:

      #define obstack_chunk_alloc xmalloc
      #define obstack_chunk_free free
Обратите внимание, что это простые макрокоманды (никаких аргументов). Определения макросов с аргументами работать не будут! Необходимо чтобы obstack_chunk_alloc или obstack_chunk_free, расширялась в имя функции, если она не является именем функции.

Функция, которая фактически осуществляет obstack_chunk_alloc, не может возвратить "отказ" в любом режиме, потому что obstack библиотека не подготовлена, чтобы обработать отказ. Следовательно, malloc непосредственно не подходит. Если функция не может получить место, она должна также завершить процесс (см. раздел 22.3 [Прерывание программ] или делать нелокальный выход, используя longjmp (см. Главу 20 [Нелокальные выходы] ).

Если Вы зарезервируете куски с malloc, размер куска, должен быть степенью 2. Заданный по умолчанию размер куска - 4096, был выбран, достаточно большим чобы удовлетворить много типичных запросов на obstack однако достаточно коротким, чтобы не тратить впустую слишком много памяти.

      size _t obstack_chunk_size (struct obstack * obstack_ptr) (макрос)
Он возвращает размер куска данного obstack.

Так как эта макрокоманда расширяется до именующего выражения, Вы можете определить новый размер куска, назначая новое значение. Ее выполнение не подействует на куски, уже размещенные, но изменит размер кусков, размещенных в том конкретном obstack в будущем., вряд ли, будет полезно сделать размер куска меньше, но создание большего могло бы увеличивать эффективность, если Вы зарезервируете много объектов, чьи размеры являются сравнимыми с размером куска. Вот как это можно сделать:

      if (obstack_chunk_size (obstack_ptr) < new_chunk_size)
       obstack_chunk_size (obstack_ptr) = new_chunk_size;

Обзор функций, имеющих отношение к obstack

Это обзор всех функций, связанных с obstack. Каждая берет в качестве первого аргумента адрес obstack (struct obstack *).

      void obstack_init (struct obstack * obstack'ptr)
Инициализирует использование obstack. См. раздел 3.4.1 [Создание obstacks].

      void * obstack_alloc (struct obstack * obstack'ptr, size_t size)
Резервирует объект как size неинициализированных байт. См. раздел 3.4.3 [Резервирование в obstack].

      void * obstack_copy (struct obstack * obstack'ptr, void *address, size_t size)
Резервирует объект из size байтов, с содержимым, скопированным из адреса address. См. раздел 3.4.3 [Резервирование в obstack].

      void * obstack_copy0 (struct obstack * obstack'ptr, void *address, size_t size)
Резервирует объект из size + 1 байт, size из которых скопированы из адреса address, сопровождаемый пустым символом в конце. См. раздел 3.4.3 [Резервирование в obstack].

      void obstack_free (struct obstack * obstack'ptr, void * object)
Освобождает обьект (и все размещенное в заданном obstack позже чем object). См. раздел 3.4.4 [Освобождение obstack объектов].

      void obstack_blank (struct obstack * obstack'ptr, size_t size)
Добавляет size неинициализированных байтов к возрастастающему обьекту object. См. раздел 3.4.6 [Возрастающие объекты].

      void obstack_grow (struct obstack * obstack'ptr, void * address,
 size _t size)
Добавляет size байт, скопированных из address, к возрастастающему обьекту object. См. раздел 3.4.6 [Возрастастающие объекты].

      void obstack_grow0 (struct obstack * obstack'ptr, void * address,
 size _t size)
Добавляет size байт, скопированных из address, к возрастастающему обьекту object, и еще добавляет другой байт, содержащий пустой символ. См. раздел 3.4.6 [Возрастастающие объекты].

      void obstack_1grow (struct obstack * obstack'ptr, char data'char)
Добавляет один байт данных к возрастастающему обьекту object. См. раздел 3.4.6 [Возрастастающие объекты].

      void * obstack_finish (struct obstack * obstack'ptr)
Завершает объект, который возрастастает и возвращает постоянный address. См. раздел 3.4.6 [Возрастающие объекты].

      size _t obstack_object_size (struct obstack * obstack'ptr)
Получает текущий размер в настоящее время возрастастающего объекта. См. раздел 3.4.6 [Возрастающие объекты].

      void obstack_blank_fast (struct obstack * obstack'ptr, size _t size)
Добавляет size неинициализированных байт к возрастастающему объекту без проверки, что имеется достаточный участок памяти. См. раздел 3.4.7 [Сверхбыстро возрастастающие объекты].

      vid obstack_1grow_fast (struct obstack * obstack'tr, char
 data'char)
Добавляет один байт к возрастастающему объекту без проверки, что имеется достаточный участок памяти. См. раздел 3.4.7 [Сверхбыстро возрастастающие объекты].

      size _t obstack_room (struct obstack * obstack'ptr)
Получает участок памяти, теперь доступный для возрастания текущего объекта. См. раздел 3.4.7 [Сверхбыстро возрастающие объекты].

      int obstack_alignment_mask (struct obstack * obstack'ptr)
Маска, используемая для выравнивания начала объекта. Это ­ именующее выражение (адрес переменной). См. раздел 3.4.9 [Выравнивание данных obstacks].

      size _t obstack_chunk_size (struct obstack * obstack'ptr)
Размер распределяемых кусков. Это - именующее выражение. См. раздел 3.4.10 [ Куски obstack].

      void * obstack_base (struct obstack * obstack'ptr)
Пробный начальный адрес в настоящее время возрастастающего объекта. См. раздел 3.4.8 [Состояние obstack].

      void * obstack_next_free (struct obstack * obstack'ptr)
Адрес следующий сразу за концом в настоящее время возрастастающего объекта. См. раздел 3.4.8 [Состояние obstack].

3.5 Автоматическая память с учетом размера переменной

Функция alloca поддерживает вид полудинамического резервирования, в котором блоки размещены динамически, но освобождаются автоматически.

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

Прототип для alloca находится в "stdlib.h". Эта функция - BSD расширение.

      void * alloca (size _t size); (функция)
Возвращаемое значение alloca - адрес блока из size байтов памяти, размещенного в области данных вызывающей функции.

Не используйте alloca внутри аргументов обращения к функции, Вы получите непредсказуемые результаты, потому что стек-пространство для alloca появится на стеке в середине пространства для аргументов функции. Пример того, что нужно избегать - foo (x, alloca (4), y).

Примеры alloca

Пример использования alloca, это функция, которая открывает имя файла, сделанное из связывания двух строк аргумента, и возвращает описатель файла или минус один выражая отказ:

      intopen2 (char * str1, char * str2, int flags, int mode)
      {
       char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
       strcpy (name, str1);
       strcat (name, str2);
       return open (name, flags, mode);
      }
А вот, как Вы получили бы те же самые результаты с malloc и free:

      intopen2 (char * str1, char * str2, int flags, int mode)
      {
       char *name = (char *) malloc (strlen (str1) + strlen (str2) + 1);
       int desc;
       if (name == 0) fatal ("превышенна виртуальная память ");
       strcpy (name, str1);
       strcat (name, str2);
       desc = (name, flags, mode);
       free (name);
       desc;
      }
Вы видите, что это более просто с alloca. Но alloca имеет другие, более важные преимущества, и некоторые недостатки.

Преимущества alloca

Имеются причины, почему alloca может быть предпочтительнее malloc:

Чтобы иллюстрировать это, предположите, что Вы имеете функцию open_or_report_error, которая возвращает описатель открытого, если она успешно завершается, но не возвращается к вызывающему оператору, если она терпит неудачу. Если файл не может быть открыт, она печатает сообщение об ошибках и переходит с командного уровня вашей программы, используя longjmp. Давайте изменим open2 (см. раздел 3.5.1 [Примеры alloca]) чтобы использовать эту подпрограмму:

      intopen2 (char * str1, char * str2, int flags, int mode)
      {
       char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
       strcpy (name, str1); strcat (name, str2);
       return open_or_report_error (name, flags, mode);
      }
Из-за способа работы alloca, память, которую она резервирует, освобождается даже, когда происходит ошибка, без специального усилия.

А предыдущее определение open2 (которое использует malloc и free) допустило бы утечку памяти, если это было бы изменено таким образом,. Даже если Вы хотите сделать большее количество изменений, чтобы устранить это, не имеется никакого простого способа делать так.

Недостатки alloca

Здесь недостатки alloca по сравнению с malloc:

GNU C массивы с переменным размером

В GNU C, Вы можете заменять большинство использований alloca с массивом переменного размера. Вот, как выглядела бы open2:

      int open2(char * str1, char * str2, int flags, int mode)
      {
       char name [strlen (str1) + strlen(str2) + 1];
       strcpy (name, str1);
       strcat (name, str2);
       return open (name, flags,mode);
      }
Но alloca не всегда эквивалентна динамическому массиву, по следующим причинам: Обратите внимание: если Вы смешиваете использование alloca и динамических массивов внутри одной функции, выход из области, в который динамический массив был объявлен, освобождает все блоки, размещенные alloca во время выполнения этой области.

3.6 Настройка программы распределения

Любая система динамического распределения памяти имеет непроизводительные затраты: количество места, которое она использует ­ больше чем количество, о котором программа просит. Программа настройки распределения памяти достигает очень низких непроизводительных затрат, перемещая блоки в памяти по мере необходимости, по собственной инициативе.

Понятия настройки резервирования

Когда Вы резервируете блок malloc, адрес блока никогда не изменяется, если Вы не используете realloc, чтобы изменить размер. Таким образом, Вы можете безопасно сохранять адрес в различных местах, временно или постоянно, как захотите. Это не безопасно, когда Вы используете программу настройки распределения памяти, потому что любые переместимые блоки могут двигаться всякий раз, когда Вы зарезервируете память в любом режиме. Даже вызов malloc или realloc может перемещать переместимые блоки.

Для каждого переместимого блока, Вы должны делать дескриптор указываемого объекта в памяти, предназначенный для того, чтобы сохранять адрес этого блока. Программа настройки распределения знает, где находится дескриптор каждого блока, и модифицирует адрес, сохраненный там всякий раз, когда она перемещает блок, так, чтобы дескриптор всегда указывал на блок. Каждый раз Вы обращаетесь к содержимому блока, Вы должны брать адрес из дескриптора.

Вызов любой функции программы настройки распределения из обработчика сигнала почти всегда неправилен, потому что сигнал мог появиться в любое время. Единственый способ делать это безопасно состоит в том, чтобы блокировать сигнал для любого доступа к содержимому любого переместимого блока, не удобен для работы. См. раздел 21.4.6 [Неповторное вхождение].

Распределение и освобождение переместимых блоков

В описаниях ниже, handleptr обозначает адрес дескриптора. Все функции объявлены в "malloc.h"; все являются расширениями GNU.

      void * r_alloc (void ** handleptr, size _t size) (функция)
Эта функция резервирует переместимый блок размера size. Она сохраняет адрес блока в * handleptr и возвращает непустой указатель в случае успеха.

Если r_alloc не может получить необходимое место, она сохраняет пустой указатель в *handleptr, и возвращает пустой указатель.

      void r_alloc_free (void ** handleptr) (функция)
Эта функция - способ освободить переместимый блок. Она освобождает блок, на который указывает *handleptr, и сохраняет пустой указатель в *handleptr, чтобы показать что он больше не указывает на размещенный блок.

      void * r_re_alloc (void ** handleptr, size _t size) (функция)
Функция r_re_alloc корректирует размер блока на который указывает *handleptr, делая его size байт длиной. Она сохраняет адрес измененного блока в *handleptr и возвращает непустой указатель в случае успеха.

Если достаточная память не доступна, эта функция, возвращает пустой указатель и не изменяет *handleptr.

3.7 Предупреждения относительно использования памяти

Вы можете просить о предупреждениях для программ исчерпывающих пространство памяти, вызывая memory_warnings. Она указывает, чтобы malloc проверял использование памяти, каждый раз когда он просит о большем количестве памяти из операционной системы. Это - расширение GNU, объявленное в "malloc.h".

      void memory_warnings (void *start, void (* warn_func) (const char*)) (функция)
Вызывайте эту функцию, чтобы запросить предупреждения для приближающегося исчерпывания виртуальной памяти.

Аргумент start говорит, где начинается место данных в памяти. Программа распределения сравнивает его c последним используемым адресом и с пределом места данных, определяя долю доступной памяти. Если Вы указываете нуль как начало, то по умолчанию, используется наиболее вероятное значение.

malloc может вызывать функцию warn_func, чтобы предупредить Вас. Она вызывается со строкой (предупреждающим сообщением) как аргументом. Обычно она должнаот образить строку для пользователя.

Предупреждения приходят, когда память становится полной на 75%, на 85%, и на 95%. Если занято более чем 95 % Вы получаете другое предупреждение каждый раз увеличивая используемую память.


Вперед Назад Содержание

Закладки на сайте
Проследить за страницей
Created 1996-2019 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру
Hosting by Ihor