Гарантирано ли е, че указателят ще запази стойността си след `delete` в C++?

Вдъхновен от този въпрос.

Да предположим, че в кода на C++ имам валиден указател и го delete правилно. Съгласно стандарта C++, указателят ще стане невалиден (3.7.3.2/4 - функцията за освобождаване ще направи невалидни всички указатели, отнасящи се до всички части на освободеното хранилище).

Поне в повечето реализации той запазва стойността и ще съхранява точно същия адрес като преди delete, но използването на стойността е недефинирано поведение.

Стандартът гарантира ли, че указателят ще запази стойността си или стойността може да се променя?


person sharptooth    schedule 15.02.2011    source източник
comment
Подписът delete позволява ли му достъп до указателя, т.е. нещо различно от преминаване по стойност?   -  person Rup    schedule 15.02.2011
comment
Интересен въпрос, но надявам се чисто академично любопитство. Не мога да си представя защо трябва да знаете това, когато пишете код.   -  person Cody Gray    schedule 15.02.2011
comment
@Коди Грей: Прав си. Използването на указателя (например опит да го printf()) след delete е UB, така че потребителят дори не може законно да прочете указателя и да го сравни с оригиналната стойност.   -  person sharptooth    schedule 15.02.2011
comment
@Rup: Delete в този случай е оператор без подпис. (Операторът delete е функцията, която се извиква от оператора delete или се извиква изрично.)   -  person Fred Nurk    schedule 15.02.2011
comment
Не мога да разбера въпроса. Можете ли да дадете някакъв пример?   -  person Harish    schedule 15.02.2011
comment
@Harish: Нещо подобно: аз delete указател и той съхранява 0xDEADBEEF адрес след това.   -  person sharptooth    schedule 15.02.2011
comment
@Коуди Грей: Това зависи - информацията, която указателят го държи, е двойна: (1) действителният му адрес и (2) къде сочи. Понякога първото може да е важно - може би не трябва - но може. Интересен въпрос.   -  person Ole Thomsen Buus    schedule 15.02.2011
comment
@FredNurk: Може би е по-ясно да се каже, че това е ключова дума, която извиква оператор (operator delete), за да делегира работата си. И че ключовата дума няма „подпис“.   -  person Lightness Races in Orbit    schedule 15.02.2011
comment
Въпреки че не е приложим на практика, това е интересен въпрос, BTW!   -  person Lightness Races in Orbit    schedule 15.02.2011
comment
@sharptooth Къде съхранява адреса?   -  person Harish    schedule 15.02.2011
comment
@Harish: В указателя това беше операндът на delete.   -  person sharptooth    schedule 15.02.2011
comment
@Fred @Tomalak: Най-правилният начин да го обозначим е като изтриване-израз. Това е израз; не е оператор, функция, делегирана функция и т.н. (Въпреки това, разбира се, има странични ефекти, като възможно извикване на operator delete и kin.)   -  person GManNickG    schedule 15.02.2011
comment
@sharptooth: Разбирам. Уникален въпрос. Нетърпелив съм да разбера отговора сам.   -  person Harish    schedule 15.02.2011
comment
@Cody Gray: това не е чисто академично, поне в смисъл, че ако стойността е променена, тогава една строго съответстваща програма може да направи разликата. Не можете да получите достъп до стойността на указателя като стойност на указателя, но можете валидно да сравните стойностите на байтовете, които заема, преди и след.   -  person Steve Jessop    schedule 15.02.2011
comment
@Steve Jessop: Ако съхраня оригиналната стойност и след това я memcmp() със стойността след delete това няма ли да е недефинирано поведение?   -  person sharptooth    schedule 15.02.2011
comment
@sharptooth: Не мисля така. memcmp не използва стойността на указателя, доколкото прочетох стандарта. Не е недефинирано поведение за memcmp 4 байта, които случайно са невалидни като плаваща стойност, така че защо трябва да е недефинирано за memcmp 4 байта, които случайно са невалидни като стойност на указател? Паметта винаги може да се изследва като байтове, при условие че все още е разпределена, няма значение какво прави или не представлява. Просто не използвайте стойността на показалеца. Резултатът от memcmp обаче може да е неуточнен или дефиниран от внедряването, независимо от това, което стандартът казва за delete на lvalues.   -  person Steve Jessop    schedule 15.02.2011
comment
@Tomalak: Имайте предвид, че операторът delete унищожава обекта, преди да извика operator delete за освобождаване на паметта.   -  person fredoverflow    schedule 15.02.2011
comment
@GMan: Правилно. / @FredOverflow: Вярно е, че не делегира цялата своя работа.   -  person Lightness Races in Orbit    schedule 16.02.2011


Отговори (7)


Не, не е гарантирано и реализация може легитимно да присвои нула на lvalue операнд на delete.

Bjarne Stroustrup се надяваше, че реализациите ще изберат да направят това, но не много го правят.

http://www.stroustrup.com/bs_faq2.html#delete-zero

person CB Bailey    schedule 15.02.2011
comment
Може ли да присвои друг адрес освен нула? - person sharptooth; 15.02.2011
comment
това е почти безполезно, тъй като всички копия на показалеца ще бъдат непроменени... - person Matthieu M.; 15.02.2011
comment
@MatthieuM.: Не казвам, че съм съгласен с Bjarne по този въпрос. stackoverflow.com/questions/1265666/ - person CB Bailey; 15.02.2011
comment
@sharptooth: Да, може да направи (почти) всичко. - person CB Bailey; 15.02.2011

Ако по някаква причина искате да сте сигурни, че променливата на показалеца не се променя от delete, напишете:

delete p + 0;
person fredoverflow    schedule 15.02.2011
comment
интересен начин, бих казал изтриване на копие на показалеца, но да, това също ще свърши работа. - person CashCow; 15.02.2011

Вярвам, че повечето реализации ще запазят стойността, само за да няма причина да я променят. Но независимо дали стойността се запазва, тя все още е безполезен указател, нали?

person Ken Wayne VanderLinde    schedule 15.02.2011

Подписът на глобалния оператор delete, както се изисква от стандарта 3.7.3.2/2:

Всяка функция за освобождаване трябва да връща void и нейният първи параметър трябва да бъде void*.

Това означава, че delete не може да промени указателя, който подавате към него, и винаги ще запази стойността си.

person Erik    schedule 15.02.2011
comment
Функционалният оператор delete не е оператор за изтриване. Сигнатурата на тази функция няма нищо общо с това дали операторът може да променя дадена стойност. (Тази функция сама по себе си не може да променя никаква стойност, разбира се. Тя дори не може да вземе указателя чрез препратка или указател към указателя, тъй като няма информация за типа от оригиналния указател.) - person Fred Nurk; 15.02.2011

Помислете как бихте проверили или разчитали на всеки отговор „да“ или „не“? не можеш Или можете, но резултатът от тази проверка (с изключение на nullpointer) е Недефинирано поведение.

Не можете да проверите ненулева стойност след delete, така че въпросът като цяло е безсмислен.

Освен това аргументът на delete може да бъде израз на rvalue, така че въпросът е безсмислен.

Наздраве и чт.,

person Cheers and hth. - Alf    schedule 15.02.2011
comment
Със сигурност можете да проверите, като разгледате стойностите на байтовете в паметта преди, запомните ги и погледнете отново след това. Вярно е, че тези байтове може да са представяне на стойност на указател преди и (да речем) представяне на прихващане след, така че в известен смисъл няма стойност след, за която можем да попитаме дали се е променила?. Но е смислено да попитате за изхода на напр. char *x = 0; char one = *(char*)(&x); delete x; char two = *(char*)(&x); std::cout << (one == two);. Ако извежда false, мисля, че е справедливо да се каже, че нещо за x се е променило, ако не точно стойността на указателя. - person Steve Jessop; 15.02.2011
comment
(Всъщност мисля, че имам нужда от char *x = new char; там, тъй като нулевите указатели са специален случай). Освен това фактът, че аргументът за delete може да бъде rvalue, няма пряко отношение към това какво се случва, когато е lvalue. Разбира се, това може да подскаже на изпълнителите, че правенето на различни неща в двата случая би било ненужно усилие. - person Steve Jessop; 15.02.2011
comment
@Steve: обикновено не е добре да се сериализират стойности на указател (напр. съхраняването им във файлове), но може да има стойност на указател в POD структура и да се сравни побитово с по-ранно копие. Мисля, че това е доста маргинален случай. така че, общо взето, безсмислено. - person Cheers and hth. - Alf; 15.02.2011
comment
В общ смисъл, освен в случаите, когато има смисъл в смисъл, че засяга поведението на строго съответстваща програма? Разбира се, не е строго съобразена програма, която вие или аз имаме някаква мотивация да напишем. Честно казано, мисля, че надеждата ви, че отговорът ви ще помогне, е празна ;-) - person Steve Jessop; 15.02.2011
comment
@Cheersandhth.-Alf: как аргументът за изтриване може да бъде израз на rvalue? Бихте ли дали пример? - person Destructor; 15.09.2015
comment
@PravasiMeet: Най-простият реалистичен пример вероятно е delete foo(), където foo е функция, която обслужва стойността на указателя. Bjarne Stroustrup, създателят на езика, също предоставя някои примери в неговия елемент с често задавани въпроси за това. - person Cheers and hth. - Alf; 15.09.2015
comment
@anonymous downvoter: обяснете какво, най-вероятно в пълно невежество, смятате, че не е наред тук. много е антисоциално да заблуждавате другите, като гласувате против напълно добри отговори като този. - person Cheers and hth. - Alf; 15.09.2015

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

Това, което може би се питате, е дали, да речем, сте правили своя собствена проверка за течове, така че сте написали функция за премахване на указател от карта, след като сте извършили изтриване. Това ще използва std::less, което гарантирано ще работи с указатели, които не сочат в рамките на диапазон, и вероятно ще работи и с указатели, които сочат към памет, която вече не е валидна.

Разбира се, може да накарате вашето събиране на боклук да направи „премахване“ точно преди да изтриете паметта, към която сочеше.

Както е със стандарта, ако стойността, която предавате за изтриване, не е l-стойност, гарантира се, че ще поддържа същата стойност, но ако е l-стойност, това е дефинирана реализация.

person CashCow    schedule 15.02.2011

Този въпрос е важен! Видях, че Visual Studio 2017 е променило стойността на показалеца след „изтриване“. Това предизвика проблем, защото използвах инструмент за проследяване на паметта. Инструментът събираше указатели след всеки оператор "нов" и ги проверяваше след "изтриване". Псевдо код:

Data* New(const size_t count)
{
    Data* const ptr(new Data[count]);
    #ifdef TEST_MODE
    DebugMemory.Collect(ptr);
    #endif
    return ptr;
}

void Delete(Data* const ptr)
{
    delete[] ptr;
    #ifdef TEST_MODE
    DebugMemory.Test(ptr);
    #endif
}

Този код работи добре на Visual Studio 2008, но се провали на Visual Studio 2017, така че промених реда на операциите във втората функция.

Въпросът обаче е добър и проблемът съществува. Опитните инженери трябва да са наясно с това.

person Łukasz Gotszald    schedule 28.07.2018