Подтвердить что ты не робот

Почему моя программа падает, когда я увеличиваю указатель и удаляю его?

Я решал некоторые упражнения по программированию, когда понял, что у меня большое недоразумение в отношении указателей. Пожалуйста, кто-нибудь может объяснить причину, по которой этот код вызывает сбой на С++.

#include <iostream>

int main()
{
    int* someInts = new int[5];

    someInts[0] = 1; 
    someInts[1] = 1;

    std::cout << *someInts;
    someInts++; //This line causes program to crash 

    delete[] someInts;
    return 0;
}

P.S Я знаю, что здесь нет оснований использовать "новое", я просто делаю пример как можно меньшим.

4b9b3361

Ответ 1

Это фактически утверждение после того, что вы отмечаете как вызывающее сбоя программы, что приводит к сбою программы!

Вы должны передать тот же указатель на delete[], когда вы вернетесь из new[].

В противном случае поведение программы undefined.

Ответ 2

Проблема в том, что с someInts++; вы передаете адрес второго элемента массива в ваш оператор delete[]. Вам необходимо передать адрес первого (оригинального) элемента:

int* someInts = new int[5];
int* originalInts = someInts; // points to the first element
someInts[0] = 1;
someInts[1] = 1;

std::cout << *someInts;
someInts++; // points at the second element now

delete[] originalInts;

Ответ 3

Не вдаваясь в специфику конкретной реализации здесь, интуитивную причину сбоя можно объяснить просто, учитывая, что delete[] должен делать:

Уничтожает массив, созданный new[] -expression

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

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

Вы можете пожелать, чтобы этот поиск привел к некоторому приветливому сообщению об ошибке, если указатель, который вы передали на delete [], не был возвращен соответствующим new[], но в этом стандарте нет ничего.

Итак, возможно, с указателем, который ранее не был выделен new[], delete[] заканчивается тем, что действительно не является последовательной структурой бухгалтерского учета. Провода пересекаются. Наступает крах.

Или вы могли бы пожелать, чтобы delete[] сказал: "Эй, похоже, что этот указатель указывает на то, что находится внутри области, которую я выделил раньше. Позвольте мне вернуться и найти указатель, который я вернул, когда я выделил этот регион и использую его для поиска бухгалтерской информации", но, опять же, в стандарте нет такого требования:

Для второй (массив) формы выражение должно быть значением нулевого указателя или значением указателя, ранее полученным формой массива нового выражения. Если выражение - это что-то еще, в том числе, если это указатель, полученный формой non-array нового выражения, поведение undefined. [акцент мой]

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

PS: Это ручное объяснение

Ответ 4

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

Однако вы должны пройти Удалить указатель, который вы получили от New. Это не инкрементная версия, а не указатель, который был выделен другими средствами.

Почему? ну, ответ об отказе, потому что это то, что говорит стандарт.

Практический ответ заключается в том, что для освобождения блока памяти менеджеру памяти нужна информация о блоке. Например, где он начинается и заканчивается, и свободны ли смежные куски (обычно диспетчер памяти объединяет смежные свободные куски) и к какой арене он принадлежит (важно для блокировки в многопоточных менеджерах памяти).

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

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

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