Почему эта программа не использует память?

#c #arrays #delete-operator

#c #массивы #оператор удаления

Вопрос:

Меня беспокоит то, что delete [] на самом деле делает, поэтому я просто попробовал некоторый код и был шокирован результатами

Тест # 1:

 int main()
{
    int *d;
    while(true)
    {
        d = new int[10];
        delete []d;
    }
}
  

Программа вообще не потребляет никакой памяти, как и ожидалось.

Тест # 2:

 int main()
{
    int *d;
    while(true)
    {
        d = new int[10];
        delete [](d   5);
    }
}
  

Хотя в каждом цикле должно быть зарезервировано не менее 20 байт (для пяти целых чисел, которые он резервирует в начале массива), которые не удаляются, эта программа также не потребляет никакой памяти!

Тест # 3:

 int main()
{
    int *d;
    while(true)
    {
        d = new int[10];
        delete []d;
        *d=1;
    }
}
  

Это вызвало нарушение доступа, как и ожидалось (кажется, вся память удалена после delete []d ).

Тест # 4:

 int main()
{
    int *d;
    while(true)
    {
        d = new int[10];
        delete [](d 5);
        *d=1;
    }
}
  

Эта была самой удивительной, хотя, хотя она не потребляет никакой памяти, программа также не создает никаких нарушений доступа, мне просто интересно, где * d хранит свои данные?

(Кстати, все программы скомпилированы без оптимизации!)

Теперь главный вопрос :

Что, если я выделил массив и закончил работу с половиной из него, не могу ли я случайно освободить эту половину и сохранить другую половину?

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

1. Все они, кроме первого, имеют неопределенное поведение. Вы не можете удалить ничего, что не было создано заново. d 5 не был новым, d был. В любом случае, как вы определяете, потребляет ли она память? Я подозреваю, что это сообщается, например, в килобайтах, и вы выделяете не более килобайта, поэтому он остается неизменным.

2. Как вы узнаете, потребляют ли программы память?

3. просто попробуйте удалить delete из каждого из них, и вы увидите, что для использования всех 4 ГБ моей оперативной памяти требуется менее 1 секунды!

4. WTF?! Вы можете удалить только то, что вы обновили. Все остальное крайне неопределенное поведение.

5. @Paul: Указатель, конечно, исчезает, но не динамически выделяемая память.

Ответ №1:

меня беспокоит, что delete [] на самом деле делает

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

Что касается этих «определенных правил о том, как правильно это использовать», тесты # 2 и # 4 вызывают неопределенное поведение:

Стандарт ISO C 2003 5.3.5 Удалить [expr.delete]

1 Оператор delete-expression уничтожает наиболее производный объект (1.8) или массив, созданный новым выражением.

     delete-expression:
        ::opt delete cast-expression
        ::opt delete [ ] cast-expression
  

Первая альтернатива — для не-массива
объекты, а второй — для массивов.
Операнд должен иметь тип указателя,
или тип класса, имеющий один
функция преобразования (12.3.2) в
тип указателя. Результат имеет тип
пустота.

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

«Неопределенное поведение» означает, что может произойти что угодно, включая поведение, которое вы только что описали.

Эти выражения, которые у вас есть в тестах # 2 и # 4, нарушают 5.3.5 / 2 и приведут к неопределенному поведению (тест # 3 также вызовет неопределенное поведение, но по другой причине).

 d = new int[10];
delete [](d   5);
  

delete[] Строка нарушает 5.3.5 / 2, потому что значение указателя, которое вы передаете delete[] , не совпадает с тем значением, которое было передано вам из new int[] .

Итак, если new int[] команда выдает вам 0xA01D2CE9 , и вы переходите в 0xA01D2CE9 5 to delete[] , вы не можете рассуждать или предсказать, что произойдет, потому что вы нарушили правила языка. То, что на самом деле произойдет, будет зависеть от того, как компилятор и / или операционная система обрабатывает new[] и delete[] . Это может варьироваться от того, что ничего плохого не происходит, до полного сбоя вашей системы, и везде между ними.

Другими словами, просто не пишите такие вещи, как delete [](d 5); .

Ответ №2:

Когда вы говорите «не потребляет никакой памяти», вы имеете в виду просмотр внутри монитора производительности в стиле «диспетчер задач»? Потому что, если это так, 40 байт не будут отображаться как «использование памяти» … вам придется выделить намного больше памяти, чтобы это отображалось в большинстве стандартных мониторов производительности процесса.

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

1. просто взглянув на мою программу, вы могли бы увидеть, что это бесконечный цикл, поэтому на каждую итерацию требуется 40 КБ, а это действительно много памяти! для использования всех 4 ГБ моей оперативной памяти требуется менее 1 секунды!

2. и если это причина, по которой тест 4 не завершается сбоем, что приводит к сбою теста 3? насколько я знаю, операционная система проверяет, изменяет ли какая-либо программа только фрагмент памяти, который она резервирует, и нарушение этого является нарушением доступа!

3. Не 40 КБ на итерацию, а 40 байт.

Ответ №3:

 delete [](d   5);
  

(из вашего теста № 2) — это определенно то, чего вам не следует делать, это приведет к повреждению памяти, если не к segfault. На моей платформе это действительно приводит к сбоям.

Результат ваших тестов будет зависеть от многих факторов, среди прочего, от платформы, на которой вы запускаете эти тесты, от внутренних компонентов new/delete и т.д.

Ответ №4:

 delete [](d   5);
  

Похоже, вы ожидаете, что это освободит только часть памяти, выделенной new int[10] .

Это не тот случай, это вызывает неопределенное поведение и может привести к тому, что произойдет что угодно.

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

Другое соображение заключается в том, что new int[10] не инициализирует выделенную память, поэтому операционная система может просто выделить некоторое адресное пространство и не нуждается в резервном выделении с помощью какого-либо физического хранилища. Это означает, что даже если вы вызываете new int[10] в цикле без какого-либо delete[] , вы можете не заметить увеличения использования памяти во многих инструментах мониторинга памяти, вплоть до того момента, когда new[] выдается std::bad_alloc исключение при исчерпании адресного пространства logicalbvg. (Вероятно, это займет некоторое время, просто выделяя 10 байт за раз.)

Ответ №5:

Вы запускаете эти тесты в рамках выпуска сборки? Вы просмотрели сгенерированные инструкции по сборке? Как вы измеряете потребление памяти? Вы просто выделяете 40 байт, а затем немедленно очищаете их.

Кроме того, выполнение чего-то подобного delete [](d 5) вызывает неопределенное поведение. Вам нужно передать указатель, возвращаемый operator new для delete корректной работы.

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

1. главный вопрос заключается в том, что, если я создаю массив и закончил работу с половиной из него, могу ли я каким-либо образом освободить то, что я занял?

2. и я сказал вам, что я не использую никаких оптимизаций!

3. Нет, вы не можете «освободить» часть массива, который вы выделили с помощью new оператора. Как упоминали другие, это приводит к неопределенному поведению . Вы также не можете этого сделать, используя C-стиль malloc() и free() либо, поскольку указатель, возвращаемый malloc() , часто указывает на скрытую структуру данных в дополнение к выделенной памяти. Затем эта скрытая структура данных используется free() для надлежащего освобождения памяти, на которую указывает указатель. Итак, если вы передадите какой-либо указатель на free() , вы также получите неопределенное поведение, поскольку любой указатель не будет иметь связанной структуры данных распределения.

4. Да, подумайте о том, как бы вы реализовали удаление. Как вы могли бы узнать, насколько велик фрагмент памяти, на который указывает указатель? Вы не можете, по крайней мере, без некоторой контекстной информации. Обычно эта информация сохраняется с некоторым смещением до того, как указатель и delete создадут резервную копию, чтобы прочитать эту информацию. У вас есть удаление, интерпретирующее по крайней мере часть ваших данных как их контекстную информацию, что означает, что они не будут работать должным образом (т. Е. неопределенное поведение)