Здравствуйте, Lis182, Вы писали:
L>Здравствуйте, SaZ, Вы писали:
L> inline ~SFile() L> { L> if (hFile != INVALID_HANDLE_VALUE) L> CloseHandle(hFile), hFile = INVALID_HANDLE_VALUE; L> }
L>[/ccode]
L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора.
σ>>Анус ставишь?
MA> Ознакомься наконец с устройством компилятора, а не выплескивай тут свою охинею.
Это не мое Вопрос был про обязанность сохранять область памяти под объект до выхода из области видимости. Естественно, если оптимизатор много повыкидывает, но это на работоспособности сказываться не должно. Скажем еслиб деструктор вообще пустым был.
A>Проблема в хреновом знании С++. A> A> Повторный вызов деструктора для экземпляра класса является UB и его использование однозначно говорит об уровне познаний аффтара кода.
Вот интерено, это где-то явно прописано?
A> Есть два варианта вызова деструктора, если используется тот, который «Virtual call», то для его корректного выполнения требуется кое-что дополнительное, в сравнении с «non-virtual call».
Какой виртуал.. В данном случае это или статичная функция, или вообще инлайново воткнутый код. Функция, которой передается 1 параметр.
A>
A>>Проблема в хреновом знании С++. A>>* Повторный вызов деструктора для экземпляра класса является UB и его использование однозначно говорит об уровне познаний аффтара кода.
L>Вот интерено, это где-то явно прописано?
http://eel.is/c++draft/class.dtor#19:
Once a destructor is invoked for an object, the object's lifetime ends; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life]).
[Example 2: If the destructor for an object with automatic storage duration is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. — end example]
AN>>и вызывать Close(), если закрыть файл нужно ещё до разрушения объекта?
L>Я примерно так и сделал. Но мне на будущее интересно знать, это ошибка компилятора, или таки в языке допустимо после деструктора портить объект.
Я начинал с ассемблера (сначала pdp-11, потом ПСшный). Основные привычки не изменились. В данном случае, я считаю, что деструктор — это просто функция с неявной передачей this. На счет выделения памяти, я был уверен, что очистка памяти произойдет только при выходе из области видимости переменной (или delete, если она создавалась через new). Я не прав получается? Хотелось бы какую-нить ссылку на документацию, где или я прав, или, допустим, "поведение не определено"
σ>http://eel.is/c++draft/class.dtor#19: σ>Once a destructor is invoked for an object, the object's lifetime ends; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life]). σ>[Example 2: If the destructor for an object with automatic storage duration is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. — end example]
Здравствуйте, Lis182, Вы писали:
MA>> Ознакомься наконец с устройством компилятора, а не выплескивай тут свою охинею. L>Это не мое Вопрос был про обязанность сохранять область памяти под объект до выхода из области видимости. Естественно, если оптимизатор много повыкидывает, но это на работоспособности сказываться не должно. Скажем еслиб деструктор вообще пустым был.
Так я и не тебе же ответил про негатив.
Ну тебе уже ответили в принципе то, в плане, что твой юз-кейс формально является UB.
Другое дело, что, компилятор натуральным образом с этой ситуацией особо ничего не может поделать (вообще-то может, например генерировать ворнинг или ошибку, но это же не будет соответствовать духу патриотизма C++), и генерируется такой псевдокод:
Как видишь, тут очень даже ясное время жизни и памяти и всего остального. И, лишь в результате оптимизаций методы могут быть "не выброшены", а, вырождены, в результате инлайнинга. Но, т.к. в реальной жизни, ты говоришь, что там у тебя вызов содержит CloseHandle, то он разумется никуда его не выбросит, кроме случая, если он докажет, что _handle == null, или, что плохо, если _handle == undef.
Но, в твоём коде _handle не будет undef, если фронтэнд не приложит к этому специальных усилий со своей стороны (например таких):
Но, ни gcc ни clang такого не делают, хотя бы потому, что оно и так еле шевелится, да и если бы всё было так просто, то на это просто банально генерировалась бы ошибка самим компилятором, и это ни разу не соответствует никакой логики.
По моему личному мнению, ты наступил или на какое-то другое UB, если ещё что-нибудь менял, или на баг.
--
В целом, вот такой код:
int my_function()
{
SMyStruct obj(123); // от твоей отличается только тем, что содержит вызов close_handle, как ты описывал
obj.~SMyStruct();
SMyStruct2 obj2(999); // подобная структура с close_handle2
obj._handle = 456;
return 0;
}
просто обязан (кому?) порождать следующий код:
my_function():
push rax
mov edi, 123
call close_handle(int)
mov edi, 999
call close_handle2(int)
mov edi, 456
call close_handle(int)
xor eax, eax
pop rcx
ret
Буквоедством то про UB заниматься можно сколько угодно, но технически, требование стандарта невозможно обеспечить кроме как в очень простых случаях, представленных выше и то этой ахинеей никто не занимается.
MA> И, лишь в результате оптимизаций методы могут быть "не выброшены", а, вырождены, в результате инлайнинга. Но, т.к. в реальной жизни, ты говоришь, что там у тебя вызов содержит CloseHandle, то он разумется никуда его не выбросит
Ой-вей, ты вообще читать умеешь? Выбрасывается не вызов CloseHandle, а присвоение после него. Выбрасывается безо всяких инлайнингов. https://godbolt.org/z/9EYKK6
Здравствуйте, σ, Вы писали:
σ>Ой-вей, ты вообще читать умеешь? Выбрасывается не вызов CloseHandle, а присвоение после него. Выбрасывается безо всяких инлайнингов. https://godbolt.org/z/9EYKK6
-fno-lifetime-dse , -flimetime-dse=N . Конкретно эта оптимизация считается довольно таки спорная. (Стоит ли объяснять что тут за этой фичей не хватает 2 тонны диагностик со стороны компилятора?)
PS: И да, про присвоение, прочитав всю ветку, я и позабыл.
L>{
L> SMyStruct obj; // выделяем память (обычно на стеке) под фактически один int
L> obj.~SMyStruct(); // Явно вызываем деструктор
L> obj.val = 123; // Это правомерно? Т.е. указатель стека обязан не увеличиваться на siseof(int) после предыдущей строки?
L>} // Тут в любом случае вызовется деструктор, которому передастся this. Собственно, вопрос тот же, обязан ли компилятор держать в памяти объект до этого места
L>
Стек работает не так, как написано в комментариях к коду. Указатель стека увеличивается на размер стека функции при её вызове и восстанавливается при завершении. Конструкторы и деструкторы на него никак не влияют.
DR>Стек работает не так, как написано в комментариях к коду. Указатель стека увеличивается на размер стека функции при её вызове и восстанавливается при завершении. Конструкторы и деструкторы на него никак не влияют.
Влияет выделение памяти. Целиком на всю функцию стековый фрейм выделяется не всегда. x86-32/64 по разному, в линуксе и винде тоже по разному. Ну а вообще данный пример можно оптимизировать, чтоб вообще стек не трогать. Я так скажем образно выразился, как бывает под x86-32.
Вот такой код законен. Но, по-моему, вы собираетесь делать какую-то фигню.
struct SMyStruct // Простейшая структурка с одним полем
{
int val; // Поле, чтоб структура не была совсем пустой
SMyStruct() {}
~SMyStruct() // Деструктор
{
val = 321; // Что-то делаем (что именно не важно). Важно, что это действие можно повторять много раз, если под объект выделена память.
}
};
{
SMyStruct obj; // выделяем память (обычно на стеке) под фактически один int
obj.~SMyStruct(); // Явно вызываем деструкторnew(&obj) SMyStruct();
obj.val = 123; // Это правомерно? Т.е. указатель стека обязан не увеличиваться на siseof(int) после предыдущей строки?
} // Тут в любом случае вызовется деструктор, которому передастся this. Собственно, вопрос тот же, обязан ли компилятор держать в памяти объект до этого места
Кстати, менять в деструкторе поля не имеет смысла -- компилятор эти действия выбросит при оптимизации.
Здравствуйте, Lis182, Вы писали:
L>Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++). Постараюсь максимально упростить вопрос, в реальности проблема немного сложнее. Приведем псевдокод, в котором собственно вопросы в комментариях.
L>
L>struct SMyStruct // Простейшая структурка с одним полем
L>{
L> int val; // Поле, чтоб структура не была совсем пустой
L> ~SMyStruct() // Деструктор
L> {
L> val = 321; // Что-то делаем (что именно не важно). Важно, что это действие можно повторять много раз, если под объект выделена память.
L> }
L>};
L>{
L> SMyStruct obj; // выделяем память (обычно на стеке) под фактически один int
L> obj.~SMyStruct(); // Явно вызываем деструктор
L> obj.val = 123; // Это правомерно? Т.е. указатель стека обязан не увеличиваться на siseof(int) после предыдущей строки?
L>} // Тут в любом случае вызовется деструктор, которому передастся this. Собственно, вопрос тот же, обязан ли компилятор держать в памяти объект до этого места
L>
Спасибо большое всем участвовавшим в обсуждении. Сейчас я понял, что по стандарту, поведение программы в данном случае не определено, т.е. UB. Падало совершенно странно, и я не был уверен, что причина именно в этом. Помогали и другие изменения кода.