Как защитить приложение от нарушения доступа на чтение при доступе к значению указателя?

#c

#c

Вопрос:

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

 void List::removeElementByValue(int value)
{
    ListMember* nextElem = this->firstValue;
    while (nextElem) {
        if (nextElem == NULL || nextElem == nullptr) {
            break;
        }

        if (nextElem->value == value) {
            if (nextElem->prevValue)
                (nextElem->prevValue)->nextValue = nextElem->nextValue;
            if (nextElem->nextValue)
                (nextElem->nextValue)->prevValue = nextElem->prevValue;
            delete nextElem;
            this->count--;
            return;
        }
        nextElem = nextElem->prevValue;
    }
}
  

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

Возникло исключение: нарушение доступа на чтение. Следующий элемент был 0XCDCDCDCDCD.

Функция в этом случае ничего не должна делать. Это происходит в этой строке:

  if (nextElem->value == value) {
  

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

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

1. Скорее всего, у вас неинициализированный указатель — чтение его значения является неопределенным поведением , вы не можете «защититься» от этого. Вам нужно убедиться, что оно инициализировано

2. Лучшая защита в C — это использование современной альтернативы необработанным указателям, таким как unique_ptr , shared_ptr или weak_ptr . Сам язык не дает вам никакой защиты от недопустимого доступа к памяти, но хороший стиль и соглашение могут.

3. Я создал некоторую цепочечную структуру — другими словами, двусвязный список, а другими словами, снова std::list<int> . Рассмотрите возможность использования этого вместо.

4. Незначительно связанные: в Википедии есть хороший список распространенных магических чисел для отладки и их значений. Когда вы получаете число, подобное 0XCDCDCDCDCD, проверьте список, пытается ли программа вам что-то сказать.

Ответ №1:

if (nextElem == NULL || nextElem == nullptr)

Это всегда будет false, когда while (nextElem) имеет значение true.

nextElem = nextElem->prevValue;

Это нужно использовать nextValue вместо prevValue .

Но, самое главное, вы не обновляете, this->firstValue если value находится в первом элементе списка, поэтому вы в конечном итоге удаляете firstValue и оставляете его указывающим на недопустимую память.

Попробуйте это вместо:

 void List::removeElementByValue(int value)
{
    ListMember* elem = this->firstValue;
    while (elem) {
        if (elem->value == value) {
            if (elem->prevValue)
                elem->prevValue->nextValue = elem->nextValue;
            if (elem->nextValue)
                elem->nextValue->prevValue = elem->prevValue;

            // ADD THIS!!!
            if (elem == this->firstValue)
                this->firstValue = elem->nextValue;

            delete elem;
            this->count--;
            return;
        }

        elem = elem->nextValue;  // NOT prevValue!
    }
}
  

Лучшее решение — в первую очередь не внедрять связанный список вручную. Вместо этого используйте стандартный std::list контейнер, пусть он сделает все самое сложное за вас.

 #include <list>

class List
{
private:
    std::list<int> values;
    ...
};

...

#include <algorithm>

void List::removeElementByValue(int value)
{
    auto iter = std::find(values.begin(), values.end(), value);
    if (iter != values.end())
        values.erase(iter);
}