Адрес, содержащий изменения указателя после удаления указателя

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

using namespace std;
#include <iostream>
#include <cstdlib>

int main()
{
    int* x = new int;
    *x = 2;

    cout << x << endl << *x << endl ;

    delete x;

    cout << x << endl;

    system("Pause");
    return 0;
}

OUTPUT:
01103ED8
2
00008123

Наблюдения: я использую Visual Studio 2013 и Windows 8. Сообщается, что это не работает в других компиляторах. Кроме того, я понимаю, что это плохая практика и что я должен просто переназначить указатель на NULL после его удаления, я просто пытаюсь понять, что ведет к этому странному поведению.

Ответ 1

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

Хорошо, почему бы и нет? Это совершенно законный выход - чтение указателя после его удаления приводит к поведению undefined. И это включает изменение значения указателя. (На самом деле, для этого даже не нужен UB, указатель delete d действительно может указывать в любом месте.)

Ответ 2

Прочитав соответствующие биты как С++ 98, так и С++ 11 [N3485], и весь материал H2CO3 указал на:

Ни один из стандартов стандарта не описывает, каков "недопустимый указатель", при каких обстоятельствах они созданы или какова их семантика. Поэтому мне непонятно, должен ли OP-код спровоцировать поведение undefined, но де-факто он делает (поскольку все, что стандарт четко не определяет, тавтологически, undefined). Текст улучшен в С++ 11, но все еще неадекватен.

В зависимости от языкового дизайна следующая программа, безусловно, демонстрирует неопределенное поведение как отмечено, что прекрасно. Он может, но не должен также демонстрировать поведение undefined как отмечено; другими словами, в той степени, в которой эта программа демонстрирует поведение undefined, то есть IMNSHO - дефект в стандарте. Конкретно, копируя значение "недопустимого" указателя и выполняя сравнения сравнений на таких указатели, не должны быть UB. Я специально отклоняю аргумент об обратном от гипотетического оборудования, который ловушки просто загружает указатель на немаркированную память в регистр. (Примечание: я не могу найти текст в С++ 11, соответствующий C11 6.5.2.3 сноске 95, относительно легитимности написания одного члена профсоюза и чтения другого, эта программа предполагает, что результат этой операции не указан, но не undefined ( за исключением того, что это может быть связано с представлением ловушки), как и в C.)

#include <string.h>
#include <stdio.h>

union ptr {
    int *val;
    unsigned char repr[sizeof(int *)];
};

int main(void)
{
    ptr a, b, c, d, e;

    a.val = new int(0);
    b.val = a.val;
    memcpy(c.repr, a.repr, sizeof(int *));

    delete a.val;
    d.val = a.val; // copy may, but should not, provoke UB
    memcpy(e.repr, a.repr, sizeof(int *));

    // accesses to b.val and d.val may, but should not, provoke UB
    // result of comparison is unspecified (may, but should not, be undefined)
    printf("b %c= d\n", b.val == d.val ? '=' : '!');

    // result of comparison is unspecified
    printf("c %c= e\n", memcmp(c.repr, e.repr, sizeof(int *)) ? '!' : '=');
 }

Это весь соответствующий текст из С++ 98:

[3.7.3.2p4] Если аргумент, присвоенный функции освобождения в стандартной библиотеке   является указателем, который не является значением нулевого указателя (4.10), функция освобождения   освобождает хранилище, на которое ссылается указатель, делает недействительным все   указатели, относящиеся к любой части освобожденного хранилища. Эффект использования   недопустимое значение указателя (включая передачу его функции освобождения)    undefined. [footnote: В некоторых реализациях она вызывает системную   ошибка времени выполнения.]

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

[24.1p5]... Итераторы также могут иметь особые значения, которые не связаны с каким-либо контейнером. [Пример: после объявления неинициализированного указателя x (как и в случае с int* x; [sic]), всегда следует предполагать, что x имеет исключительное значение указателя.] Результаты большинства выражений undefined для сингулярные значения; единственным исключением является присвоение неособым значения итератору, который имеет сингулярное значение. В этом случае сингулярное значение перезаписывается так же, как любое другое значение. Разнообразные и прошедшие значения всегда неособые.

По-видимому, кажется правдоподобным предположить, что "недопустимый указатель" также должен быть примером "единственного итератора", но нет текста, чтобы поддержать это; в противоположном направлении нет текста, подтверждающего (в равной степени правдоподобное) предположение, что неинициализированное значение указателя означает "недействительный указатель", а также "сингулярный итератор". Таким образом, расщепители волос среди нас могут не принимать "результаты большинства выражений undefined", как уточнение того, что квалифицируется как использование недопустимого указателя.

С++ 11 несколько изменил текст, соответствующий 3.7.2.3p4:

[3.7.4.2p4]... Направление с помощью недопустимого значения указателя и передача недопустимого значения указателя функции деаллокации имеют поведение undefined. Любое другое использование Недопустимое значение указателя имеет поведение, определяемое реализацией. [footnote: Некоторые реализации могут определять, что копирование недопустимого значения указателя вызывает системную ошибку выполнения.]

(текст, выходящий за пределы эллипсиса, не изменился). Теперь мы имеем несколько более ясную информацию о том, что подразумевается под "использованием недопустимого значения указателя", и теперь мы можем сказать, что семантика OP-кода определенно определена реализацией ( но может быть определено как undefined). В обсуждении итераторов есть также новый абзац:

[24.2.1p10] Недействительный итератор - это итератор, который может быть сингулярным.

который подтверждает, что "недействительный указатель" и "сингулярный итератор" являются фактически одним и тем же. Оставшаяся путаница в С++ 11 в значительной степени касается точных обстоятельств, которые приводят к недействительным/особым указателям/итераторам; должна быть подробная диаграмма переходов жизненного цикла указателя/итератора (например, для значений *). И, как и в случае с С++ 98, стандарт является дефектным, поскольку он не гарантирует, что сопоставление копирования и равенства по таким значениям действительно (не undefined).