Бьерн Страуструп - Язык программирования С++. Главы 2-4 - Свободная память

ОГЛАВЛЕНИЕ

 

3.2.6  Свободная память

Именованный объект является либо статическим, либо автоматическим (см.$$2.1.3). Статический объект размещается в памяти в момент запуска программы и существует там до ее завершения. Автоматический объект размещается в памяти всякий раз, когда управление попадает в блок, содержащий определение объекта, и существует только до тех пор, пока управление остается в этом блоке. Тем не менее, часто бывает удобно создать новый объект, который существует до тех пор, пока он не станет ненужным. В частности, бывает удобно создать объект, который можно использовать после возврата из функции, где он был создан. Подобные объекты создает операция new, а операция delete используется для их уничтожения в дальнейшем. Про объекты, созданные операцией new, говорят, что они размещаются в свободной памяти. Примерами таких объектов являются узлы деревьев или элементы списка, которые входят в структуры данных, размер которых на этапе трансляции неизвестен. Давайте рассмотрим в качестве примера набросок транслятора, который строится аналогично программе калькулятора. Функции синтаксического анализа создают из представлений выражений дерево, которое будет в дальнейшем использоваться для генерации кода. Например:

       struct enode {
          token_value oper;
          enode* left;
          enode* right;
       };

      enode* expr()
      {
        enode* left = term();

        for(;;)
           switch(curr_tok) {
             case PLUS:
             case MINUS:
                  get_token();
                  enode* n = new enode;
                  n->oper = curr_tok;
                  n->left = left;
                  n->right = term();
                  left = n;
                  break;
            default:
                 return left;
           }
        }

Генератор кода может использовать дерево выражений, например так:

       void generate(enode* n)
       {
         switch (n->oper) {
         case PLUS:
              // соответствующая генерация
              delete n;
         }
       }

Объект, созданный с помощью операции new, существует, до тех пор, пока он не будет явно уничтожен операцией delete. После этого память, которую он занимал, вновь может использоваться new. Обычно нет никакого "сборщика мусора", ищущего объекты, на которые никто не ссылается, и предоставляющего занимаемую ими память операции new для повторного использования. Операндом  delete может быть только указатель, который возвращает операция new, или нуль. Применение delete к нулю не приводит ни к каким действиям.

Операция new может также создавать массивы объектов, например:

        char* save_string(const char* p)
        {
          char* s = new char[strlen(p)+1];
          strcpy(s,p);
          return s;
        }

Отметим, что для перераспределения памяти, отведенной операцией new, операция delete должна уметь определять размер размещенного объекта. Например:

        int main(int argc, char* argv[])
        {
          if (argc < 2) exit(1);
          char* p = save_string(arg[1]);
          delete[] p;
        }

Чтобы добиться этого, приходится под объект, размещаемый стандартной операцией new, отводить немного больше памяти, чем под статический (обычно, больше на одно слово). Простой оператор delete уничтожает отдельные объекты, а операция delete[] используется для уничтожения массивов.

Операции со свободной памятью реализуются функциями ($$R.5.3.3-4):

        void* operator new(size_t);
        void operator delete(void*);

Здесь size_t - беззнаковый целочисленный тип, определенный в <stddef.h>.

Стандартная реализация функции operator new() не инициализирует предоставляемую память.

Что случится, когда операция new не сможет больше найти свободной памяти для размещения? Поскольку даже виртуальная память небесконечна, такое время от времени происходит. Так, запрос вида:

       char* p = new char [100000000];

обычно не проходит нормально. Когда операция new не может выполнить запрос, она вызывает функцию, которая была задана как параметр при обращении к функции set_new_handler() из <new.h>. Например, в следующей программе:

      #include <iostream.h>
      #include <new.h>
      #include <stdlib.h>

      void out_of_store()
      {
         cerr << "operator new failed: out of store\n";
         exit(1);
      }

      int main()
      {
        set_new_handler(&out_of_store);
        char* p = new char[100000000];
        cout << "done, p = " << long(p) << '\n';
      }

скорее всего, будет напечатано не "done", а  сообщение:

      operator new failed: out of store
      // операция new не прошла: нет памяти

С помощью функции new_handler можно сделать нечто более сложное, чем просто завершить программу. Если известен алгоритм операций new и delete (например, потому, что пользователь определил свои функции
operator new и operator delete), то обработчик new_handler может попытаться найти свободную память для new. Другими словами, пользователь может написать свой "сборщик мусора", тем самым сделав вызов операции delete необязательным. Однако такая задача, безусловно, не под силу новичку.

По традиции операция new просто возвращает указатель 0, если не удалось найти достаточно свободной памяти. Реакция же на это new_handler не была установлена. Например, следующая программа:

     #include <stream.h>

     main()
     {
       char* p = new char[100000000];
       cout << "done, p = " << long(p) << '\n';
     }

выдаст

     done, p = 0

Память не выделена, и вам сделано предупреждение! Отметим, что, задав реакцию на такую ситуацию в функции new_handler, пользователь берет на себя проверку: исчерпана ли свободная память. Она должна выполняться при каждом обращении в программе к new (если только пользователь не определил собственные функции для размещения объектов пользовательских типов; см.$$R.5.5.6).