как обработать удаление по нелегальному адресу - PullRequest
0 голосов
/ 30 апреля 2010

Предположим, у нас такая ситуация. Предположим, вместо "p = & global;" мы вызвали некоторую функцию (написанную кем-то, кто лишает нас законной силы указателя). Как справиться с этой проблемой? Как защитить код от сбоев? Я знаю о буст-умных указателях и использую их. Но что делать, если у нас такая ситуация?

struct Test
{
    int a;
    int b;
    int c;
};

Test global;

int main()
{
    Test *p = new Test;

    p->a = 1;
    p->b = 2;
    p->c = 3;

    p = &global;

    delete p;

    return 0;
}

Ответы [ 8 ]

17 голосов
/ 30 апреля 2010

Вы справляетесь с этим, исправляя ошибку и перекомпилируя вашу программу. Все остальное не имеет смысла.

9 голосов
/ 30 апреля 2010

Вы не можете и не должны пытаться справиться с этой ситуацией иначе, чем не допустить ее возникновения.
В C ++ есть несколько основных правил, которым просто нужно следовать.

5 голосов
/ 30 апреля 2010

Ничего. Если вы сделаете это, то получите то, что получили. Не делай этого.

После переназначения p происходит утечка объекта Test, на который изначально указывал p. Вы потеряли эту память на время выполнения этого приложения. Затем, когда вы удаляете объект без кучи, вы сталкиваетесь с неопределенным поведением, и может произойти все что угодно (обычно библиотека времени выполнения аварийно завершает работу, пытаясь удалить память без кучи - но у вас нет никаких гарантий). Нет абсолютно ничего надежного, что вы можете сделать после того, как попытаетесь удалить память без кучи.

Вы уже упомянули умные указатели, которые являются частью решения. Другая часть просто осторожна.

4 голосов
/ 30 апреля 2010

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

Короче говоря, C ++ не может защитить вас от всех возможных ошибок, которые вы можете написать.

2 голосов
/ 30 апреля 2010

Вы можете использовать код ниже, чтобы узнать, указывает ли указатель на область стека или область кучи:

bool IsMemoryOnStack( void* p )
{
    void* dwStackTop = 0;
    void* dwStackLowCurrent = 0;
    __asm  {
        mov EAX, FS:[4]
        mov dwStackTop, eax
            mov EAX, FS:[8]
            mov dwStackLowCurrent, eax
    }

    return ( p<= dwStackTop && p>= dwStackLowCurrent );
}
1 голос
/ 30 апреля 2010

Основной способ избежать этого - просто избегать использования new или delete при любых, но наиболее строго контролируемых обстоятельствах.

Использование new внутри main особенно подозрительно - причина использования new в C ++ - это когда вам нужно создать объект, который должен пережить область, в которой он находится создано (например, когда вы достигнете конца этой функции, она должна быть не уничтожена). В случае main, только причина делать то, что было бы, если бы вы выделяли что-то в main, что не будет удалено в main, но используется некоторыми деструктор какого-то глобального объекта, который запускался после того, как вы вернулись из main (что редко делается и еще реже хорошая идея).

Большинство случаев использования new должно быть в ctor объекта, а большинство случаев использования delete должно быть в dtor объекта. Если у вас есть что-то вроде коллекции, может также иметь смысл использовать new и / или delete в некоторых других функциях-членах, которые обрабатывают такие вещи, как изменение размера коллекции.

Кроме этого, существуют объекты-сущности, которые обычно никогда не назначаются и не копируются. Например, рассмотрим систему маршрутизации вызовов, в которой вы создаете объект «вызов», когда кто-то набирает свой телефон, и уничтожаете объект, когда он вешает трубку. Практически в каждом таком случае у вас есть глобальная коллекция (или несколько), в которой хранятся указатели на эти объекты, поэтому, как только вы создаете объект, его адрес попадает в (правильную) глобальную коллекцию. Интересный момент: почти весь код, который я видел, где этот шаблон имел смысл, имел , а не какой-либо внешний код, который разрушил объект - скорее, сам объект отвечал за удаление себя из глобального соединения, и используя (много спорили, о) delete this; уничтожить себя, когда в реальном мире соединение (или любой другой), он был прикреплен к закончился.

1 голос
/ 30 апреля 2010

Вам необходимо поменять местами присваивание и удалить операторы:

 delete p;
 p = &global;

НО я бы предложил никогда не использовать одну и ту же переменную для указания на данные, которые требуют явного освобождения, и данные, которые не требуют. Выберите одну или другую для каждой переменной, чтобы вы всегда могли удалить память перед ее переназначением или никогда не удалять ее. Если вы попытаетесь отследить, как установлены ваши указатели, вы все время будете жаловаться на то, что C ++ не обеспечивает управление памятью и заставляет вас писать неуправляемый код.

0 голосов
/ 30 апреля 2010

Возможна перегрузка delete.Теоретически, ваш перегруженный delete может отказаться от каких-либо действий, если адрес не действителен.Но как узнать, действительно ли это?Лучшее, что вы можете сказать: «это не было выделено new», но вам, вероятно, придется перегрузить new, чтобы сделать это.

Для записи, стандартные new и delete сбой в этом случае, потому что delete определяет, что адрес не пришел от new и предполагает худшее.Предполагая, что худшее, вероятно, лучше всего делать в этой ситуации;по крайней мере, это лучше, чем предполагать лучшее.

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

...