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

Разрешено ли удалению изменять его параметр?

В ответе qaru.site/info/9629/... есть цитата из Stroustrup:

С++ явно позволяет реализовать удаление, чтобы обнулить lvalue operand, и я надеялся, что реализация этого сделает, но эта идея, похоже, не стала популярной среди разработчиков.

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

6.7

Когда достигнут конец продолжительности области хранения, значения всех указателей, представляющих адрес любой части этого область хранения становится недопустимой величиной указателя (6.9.2). косвенность через недопустимое значение указателя и передачу недопустимого значения указателя к функции дезадаптации не удалось определить поведение. Любое другое использование неверное значение указателя имеет поведение, определяемое реализацией.

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

Итак, после значения delete ptr;, ptr становится недопустимым значением указателя, и использование этого значения имеет поведение, определенное реализацией. Тем не менее, он не говорит, что значение ptr разрешено изменять.

Это может быть философский вопрос, как можно решить, изменилось ли значение, если нельзя использовать его значение?

6,9

Для любого объекта (кроме подобъекта базового класса) тривиально тип копирования T, независимо от того, имеет ли объект допустимое значение типа T, базовые байты (4.4), составляющие объект, могут быть скопированы в массив из char, unsigned char или std:: byte (21.2.1).43 Если содержимое этого массива копируется обратно в объект, объект должен впоследствии сохраняют свое первоначальное значение.

Итак, похоже, что значение memcpy недопустимо для указателя в массив char (в зависимости от того, какой оператор "сильнее", 6.7 или 6.9). Для меня 6.9 кажется более сильным).

Таким образом, я могу обнаружить, что значение указателя было изменено на delete: memcpy значение указателя до и после массива delete до char, а затем сравнить их.

Итак, как я понимаю, 6.7 не гарантирует, что delete разрешено изменять его параметр.

Разрешено ли разрешению изменять его параметр?

Ознакомьтесь с комментариями здесь: qaru.site/info/166494/...


Здесь маловероятный, но все же возможный код реального мира, где это имеет значение:

SomeObject *o = ...; // We have a SomeObject
// This SomeObject is registered into someHashtable, with its memory address
// The hashtable interface is C-like, it handles opaque keys (variable length unsigned char arrays)

delete o;

unsigned char key[sizeof(o)];
memcpy(key, &o, sizeof(o)); // Is this line OK? Is its behavior implementation defined?
someHashtable.remove(key, sizeof(key)); // Remove o from the hashtable

Конечно, этот фрагмент может быть переупорядочен, поэтому он становится достоверным кодом. Но вопрос в том, что это действительный код?


Вот связанный ход мысли: предположим, что реализация определяет, что описывает сноска:

копирование недопустимого значения указателя вызывает системную ошибку выполнения

6.9 гарантирует, что я могу memcpy() любое значение. Даже недействительный. Таким образом, в этой теоретической реализации, когда я memcpy() значение недопустимого указателя (что должно быть успешным, 6.9 гарантирует это), в некотором смысле я не использую недопустимое значение указателя, а только его базовые байты (потому что он будет генерировать runtime fault и 6.9 не позволяет), , поэтому 6.7 не применяется.

4b9b3361

Ответ 1

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

В вашем вопросе есть основное заблуждение; вы объединяете эти две разные концепции:

  • Значение переменной
  • Представление переменной в памяти.

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


Я думаю, что суть вашего вопроса: can delete ptr; изменить представление ptr?. На что ответ "Да". Вы могли бы memcpy удалить указатель в массив char, проверить байты и найти их все равными нулю значками байтов (или что-нибудь еще). Это описано в стандарте С++ 14 [basic.stc.dynamic.deallocation]/4:

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

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


Ваш фрагмент кода зависит от поведения, определенного для реализации. "Действительный код" не является терминологией, используемой Стандартом, но код может не удалять предполагаемый элемент из хеш-таблицы.

Как упоминалось в Stroustrup, это намеренное дизайнерское решение. Примером использования может быть компилятор в режиме отладки, который устанавливает удаленные указатели на конкретное представление, чтобы он мог вызвать ошибку времени выполнения, если впоследствии используется удаленный указатель. Вот пример этого принципа в действии для неинициализированных указателей.

Историческое примечание. В С++ 11 этот случай был undefined, а не определялся реализацией. Таким образом, поведение использования удаленного указателя было идентично поведению использования неинициализированного указателя. На языке C освобождение памяти определяется как включение всех указателей в эту память в то же состояние, что и неинициализированный указатель.

Ответ 2

delete определяется в [expr.delete] для вызова функции деаллоляции, а функции освобождения определены в [basic.stc.dynamic.deallocation] как:

Каждая функция освобождения должна возвращать void, а ее первый параметр должен быть void*.

Так как все функции дезадаптации получают void*, а не a void*&, нет механизма для их возможности изменять свои параметры.

Ответ 3

Контекст, в котором вы нашли выражение от Stroustrup, доступен под Stroustrup, удалить нуль

Stroustrup позволит вам рассмотреть

delete p;
// ...
delete p;

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

Идея Stroustrups заключалась в том, чтобы скомпилировать ее как-то вроде

delete p; p = 0;
// ...
delete p;

delete сам не может обнулить указатель, поскольку он прошел void *, но не void *&

Однако я считаю, что нуль p не очень помогает, поскольку они могут содержать другие копии этого указателя, которые могут быть случайно удалены. Лучше всего использовать различные типы интеллектуальных указателей.

Spec 6.7

Говорит, что любой указатель с адресом указателя p будет недопустимым (в определенном порядке реализации) после удаления p. Он ничего не говорит об изменении адреса указателя, ни разрешен, ни запрещен.

Spec 6.9

Предпосылкой 6.9 является объект (действительный или нет). Эта спецификация здесь не применяется, поскольку p (адрес) недействителен после удаления и поэтому НЕ указывает на объект. Таким образом, нет противоречия, и любое обсуждение о том, сильнее ли 6.7 или 6.9, неверно.

Спецификация требует также скопировать байты обратно в исходное местоположение объекта, которое не имеет вашего кода, и также не сможет, поскольку исходный объект был удален.


Однако я не вижу причин, чтобы упаковать адрес указателя в массив char и передать его. И указатели всегда имеют одинаковый размер в определенной реализации. Ваш код является просто громоздкой версией:

SomeObject *o = ...;

void *orgO = o;
delete o;

someHashtable.remove(orgO);
// someHashtable.remove(o); o might be set to 0

Однако этот код по-прежнему выглядит странно. Чтобы получить объект из хеш-таблицы, вам нужен указатель на этот объект. Почему напрямую не использовать указатель напрямую?

Хэш-таблица должна помогать находить объекты по некоторым инвариантным значениям объектов. Это не ваше приложение хеш-таблицы

Вы намерены иметь список всех допустимых экземпляров SomeObject?

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

Ответ 4

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

Теперь представьте себе цепочку функций, которые передают указатель друг другу в аргументы, и только последний действительно удалит. Какие указатели для обновления в таком случае? Последний? Все? Для последнего нужно создать динамические списки указателей:

Objec *o = ...
handle(o);
void handle(Object *o){
   if (deleteIt) doDelete(0);
   else doSomethingElseAndThenPossiblyDeleteIt(o);
}
void doDelete(Object *o) {
    delete o;
}

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

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

Ответ 5

Отказ от недопустимого значения указателя и передача недопустимого значения указателя функции деаллокации имеют поведение undefined. Любое другое использование недопустимого значения указателя имеет поведение, определяемое реализацией.

Итак, после значения delete ptr;, ptr становится недопустимым значением указателя, и использование этого значения имеет поведение, определенное реализацией.

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

Язык не очень ясен, но вот контекст:

6.7 Длительность хранения
4 По достижении конца продолжительности области хранения значения всех указателей, представляющих адрес любой части этого региона хранилища, становятся недопустимыми значениями указателя (6.9.2). Отказ от недопустимого значения указателя и передача недопустимого значения указателя функции деаллокации имеют поведение undefined. Любое другое использование недопустимого значения указателя имеет поведение, определяемое реализацией.

6.9.2 Составные типы
Каждое значение типа указателя является одним из следующих:
(3.1) - указатель на объект или функцию (указатель, как говорят, указывает на объект или функцию), или
(3.2) - указатель мимо конца объекта (8.7), или
(3.3) - значение нулевого указателя (7.11) для этого типа, или
(3.4) - недопустимое значение указателя.

Это значения указателя типа, которые являются или не являются недопустимыми, и они "становятся" таким образом в соответствии с прогрессом программы на абстрактной машине С++.

В стандарте не говорится об изменениях того, какое значение удерживает переменная/объект, адресуемая lvalue, или изменяется на ассоциацию символа со значением.

С++ явно позволяет реализовать удаление, чтобы обнулить   lvalue operand, и я надеялся, что реализация этого сделает,   но эта идея, похоже, не стала популярной среди разработчиков.

Отдельно от этого, Stroustrup говорит, что если выражение операнда было модифицируемым значением lvalue, то есть выражение операнда было адресом переменной/объекта, содержащим переданное значение указателя, после чего статус этого значения является "недействительным", то реализация может установить значение, удерживаемое этой переменной/объектом, равным нулю.

Однако он не говорит, что значение ptr разрешено изменять.

Stroustrup неформален, говоря о том, что может сделать реализация. Стандарт определяет, как абстрактная машина С++ может/не может/может вести себя. Здесь Страуструп говорит о гипотетической реализации, которая похожа на эту машину. Значение "ptr" "разрешено изменять", поскольку определенное и undefined поведение не позволяет вам узнать, какое это значение имеет значение для освобождения, а поведение, определяемое реализацией, может быть любым, поэтому оно может быть что переменная/объект имеет другое значение.

Не имеет смысла говорить о изменении ценности. Вы не можете "обнулить" значение; вы можете обнулить переменную/объект, и что мы имеем в виду, когда говорим "zero out" lvalue - нулевую переменную/объект, который она ссылается/идентифицирует. Даже если вы растягиваете "ноль", чтобы включить ассоциирование нового значения с именем или литералом, реализация может это сделать, потому что вы не можете "использовать" значение во время выполнения через имя или литерал, чтобы выяснить, все ли оно связано с то же значение.

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

Если содержимое этого массива будет скопировано обратно в объект, объект впоследствии сохранит свое исходное значение.

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

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

Ничто не делает то, что обычно делает с undefined/определенным положением. Мы используем обычное поведение для определения последовательности изменений в абстрактной машине, и если возникает изменение состояния, определяемое реализацией, все происходит так, как реализация определяет их действие, а не то, как они обычно это делают. К сожалению, смысл "использования" значения не уточняется. Я не знаю, почему вы считаете, что 6.9 "гарантирует" что-либо более или менее re memcpy, чем где-либо еще, что после состояния undefined/реализаций ничего.