Автоматическая очистка кучи при уничтожении стека

#c #c #memory #allocation

#c #c #память #выделение

Вопрос:

 int* f()
{
    int *p = new int[10];
    return p;
}

int main()
{
    int *p = f();
    //using p;
    return 0;
}
  

Правда ли, что во время уничтожения стека, когда функция возвращает это значение, некоторые компиляторы (когда мне сказали, что подразумевались обычные компиляторы, такие как VS или gcc) могут попытаться автоматически освободить память, на которую указывают локальные указатели, такие как p в этом примере? Даже если это не так, смогу ли я нормально удалить [] выделенную память в main? Проблема, по-видимому, заключается в том, что в этот момент теряется информация о точном размере массива. Кроме того, изменится ли ответ в случае malloc и free?

Спасибо.

Ответ №1:

Уничтожаются-освобождаются только локальные переменные.

В вашем случае p «уничтожено» (освобождено) , но то, на что p указывает, не «уничтожено» (освобождено с помощью delete[] ).

Да, вы можете и должны / must использовать delete[] на вашем main. Но это не подразумевает использование необработанных указателей в C . Возможно, вам покажется интересной эта электронная книга: Ссылка-Alf-Book

Если вы хотите узнать, delete на что указывает локальная переменная, когда функция «закончена» (вне области видимости), используйте std::auto_ptr() (работает только для переменных, отличных от массива, но не для тех, которые требуют delete[] )

Кроме того, изменится ли ответ в случае malloc и free?

Нет, но вы должны убедиться, что вы не смешиваете free()/new/delete/malloc() . То же самое относится к new/delete[] и new[]/delete .

Ответ №2:

Нет, они не будут, free или delete на что указывает ваш указатель. Они освободят только те несколько байтов, которые занимает сам указатель. Компилятор, который вызвал free or delete , я полагаю, нарушил бы языковой стандарт.

Вы сможете использовать delete[] память main только в том случае, если у вас есть указатель на память, то есть результат из f() . Вам не нужно отслеживать размер выделения; new и malloc сделайте это за себя, за кулисами.

Если вы хотите, чтобы память очищалась при возврате функции, используйте интеллектуальный указатель, такой как boost::scoped_ptr или boost::scoped_array (оба из коллекции библиотек Boost), std::auto_ptr (в текущем стандарте C , но скоро устареют) или std::unique_ptr (в готовящемся стандарте).

В C невозможно создать интеллектуальный указатель.

Ответ №3:

Правда ли, что во время уничтожения стека, когда функция возвращает это значение, некоторые компиляторы (когда мне сказали, что подразумевались обычные компиляторы, такие как VS или gcc) могут попытаться автоматически освободить память, на которую указывают локальные указатели, такие как p в этом примере?

Короткий ответ: Нет

Длинный ответ:

Если вы используете интеллектуальные указатели или контейнер (как и должно быть), тогда да.
Когда интеллектуальный указатель выходит за пределы области видимости, память освобождается.

 std::auto_ptr<int> f()
{
    int *p = new int;
    return p; // smart pointer credated here and returned.
              // p should probably have been a smart pointer to start with
              // But feeling lazy this morning.
}

std::vector<int> f1()
{
    // If you want to allocate an array use a std::vector (or std::array from C  0x)
    return std::vector<int>(10);
}

int main()
{
    std::auto_ptr<int> p = f();
    std::vector<int>   p1 = f1();

    //using p;
    return 0;  // p destroyed
}
  

Даже если это не так, смогу ли я нормально удалить [] выделенную память в main?

Обычно необходимо убедиться, что вся память правильно освобождена, как только она вам не понадобится.
Note delete [] и delete отличаются друг от друга, поэтому будьте осторожны при их использовании.

Память, выделенная с помощью new , должна быть освобождена с помощью delete .
Память, выделенная с помощью new [] , должна быть освобождена с помощью delete [] .
Память, выделенная с помощью malloc/calloc/realloc , должна быть освобождена с помощью free .

Проблема, по-видимому, заключается в том, что в этот момент теряется информация о точном размере массива.

Запоминание этой информации является проблемой систем времени выполнения. Способ ее хранения стандартом не указан, но обычно она близка к объекту, который был выделен.

Кроме того, изменится ли ответ в случае malloc и free?

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

Ответ №4:

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

Не беспокойтесь о том, как компилятор отслеживает, сколько элементов было выделено, это деталь реализации, которая не рассматривается стандартом C . Просто знайте, что это работает. (При условии, что вы используете delete[] обозначения, которые вы сделали)

Комментарии:

1. Действительно ли компилятор или операционная система отслеживает количество выделенных байтов?

2. @Blagovest Буюклиев, компилятор отвечает. Если он делегирует эту ответственность операционной системе, это ее дело.

Ответ №5:

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

Ответ №6:

Я не слышал о компиляторе, делающем это, но компилятор, безусловно, может определить (во многих случаях), не ссылается ли указатель на выделенную память из функции, а затем освободить эту память.

Однако в вашем случае память не теряется, потому что вы сохраняете указатель на нее, который является возвращаемым значением функции.

Очень распространенный случай утечек памяти и идеальным кандидатом для такой функции был бы этот код:

 int *f()
{
    int *p = new int[10];
    // do something that doesn't pass p to external
    // functions or assign p to global data
    return p;
}

int main()
{
    while (1) {
      f();
    }
    return 0;
}
  

Как вы можете заметить, указатель на выделенную память теряется, и это может быть обнаружено компилятором с абсолютной уверенностью.

Комментарии:

1. Конечно, компилятор не может определить это с уверенностью. Что, если указатель на выделенный блок передается функции в другой единице преобразования? Функция может делать с указателем все, что угодно, включая создание static копии. Фактически, компилятору даже не нужно знать, откуда взята память new или malloc . Из-за этого так сложно написать сборщик мусора для C / C .

2. Однако здесь легко обмануть, например, p1 = p; p = NULL; вернуть p1; или вернуть указатель каким-либо другим способом.

3. @larsmans: верно, но в большинстве типичных случаев, подобных int *f() { int *p = new int[10]; return NULL; } , это может сделать компилятор.

4. Компилятор мог бы сделать это, только если он может доказать, что не существует возможной допустимой ссылки на указатель. Если вы передали указатель на какие-либо внешние функции, это в принципе невозможно. И использование printf("%p", ptr); полностью исключает это.

5. @R ..: в целом верно, но многие компиляторы реализованы для понимания семантики функций из стандартной библиотеки, так что printf подобная функция не должна сохранять статический указатель на свои аргументы. И я не говорю, что обнаружение этого возможно во всех случаях, просто в некоторых случаях это можно обнаружить с абсолютной уверенностью.