Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++). Постараюсь максимально упростить вопрос, в реальности проблема немного сложнее. Приведем псевдокод, в котором собственно вопросы в комментариях.
struct SMyStruct // Простейшая структурка с одним полем
{
int val; // Поле, чтоб структура не была совсем пустой
~SMyStruct() // Деструктор
{
val = 321; // Что-то делаем (что именно не важно). Важно, что это действие можно повторять много раз, если под объект выделена память.
}
};
{
SMyStruct obj; // выделяем память (обычно на стеке) под фактически один int
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>
Да, память под ваш obj будет выделена на стеке и освобождена только при выходе из области видимости переменной. Но деструктор будет вызван дважды. Это очень хороший способ чтобы накосячить.
Я правильно понимаю, что вы пытаетесь прибить гвознями какой-то фикс вместо того чтобы правильно спроектировать класс?
Здравствуйте, Lis182, Вы писали:
L>Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++).
Формализм в данном случае предполагает, что есть некое формальное требование к компилятору, которому он должен соответствовать, дабы иметь заявленной поддержку некоего стандарта, а который данное требование входит.
Внимание вопрос, а в каком из стандартов сказано, что компилятор обязан отслеживать ручной вызов деструктора у объектов?
Второй вопрос, а исходя из чего с формальной точки зрения сделано заключение, что в приводимом примере экземпляр объекта должен будет существовать именно на стэке?
Здравствуйте, a7d3, Вы писали:
A>Здравствуйте, Lis182, Вы писали:
L>>Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++).
A>Формализм в данном случае предполагает, что есть некое формальное требование к компилятору, которому он должен соответствовать, дабы иметь заявленной поддержку некоего стандарта, а который данное требование входит. A>Внимание вопрос, а в каком из стандартов сказано, что компилятор обязан отслеживать ручной вызов деструктора у объектов? A>Второй вопрос, а исходя из чего с формальной точки зрения сделано заключение, что в приводимом примере экземпляр объекта должен будет существовать именно на стэке?
Деструкторов то отслеживать как раз не надо. Надо только гарантировать, что память не освободится, и в деструктор будет передаваться корректный this. На счет стека, то это сказано скорее образно. Понятно, что где и как выделена память касается только компилятора (или интерпретатора, который недавно писал).
Здравствуйте, SaZ, Вы писали:
SaZ>Здравствуйте, 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>>
SaZ>Да, память под ваш obj будет выделена на стеке и освобождена только при выходе из области видимости переменной. Но деструктор будет вызван дважды. Это очень хороший способ чтобы накосячить. SaZ>Я правильно понимаю, что вы пытаетесь прибить гвознями какой-то фикс вместо того чтобы правильно спроектировать класс?
Спасибо за ответ Не совсем прибить гвоздями. Это мой старый код. Деструктор был таким:
Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.
Здравствуйте, Lis182, Вы писали:
L>Деструкторов то отслеживать как раз не надо. Надо только гарантировать, что память не освободится, и в деструктор будет передаваться корректный this. На счет стека, то это сказано скорее образно. Понятно, что где и как выделена память касается только компилятора (или интерпретатора, который недавно писал).
Тогда ещё раз, тоже самое, но другими словами.
Компиляция выполняется в результате многократного числа проходов по коду. На каждом из проходов что-то добавляется, что-то упрощается или выкидывается. Каждый проход использует результаты предыдущих. А теперь добавляем в код руками то самое, что могло или должно было быть добавлено в результате одного из таких проходов. Как именно тогда будет действовать компилятор?
L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.
A>Тогда ещё раз, тоже самое, но другими словами. A>Компиляция выполняется в результате многократного числа проходов по коду. На каждом из проходов что-то добавляется, что-то упрощается или выкидывается. Каждый проход использует результаты предыдущих. А теперь добавляем в код руками то самое, что могло или должно было быть добавлено в результате одного из таких проходов. Как именно тогда будет действовать компилятор?
Спасибо за ответ Но деструктор это функция, и как любая функция должен вызываться если это указано в коде. У меня только сомнения в обязательности хранения объекта до выхода из области видимости. У меня этот код отлично работал с разными компиляторами много лет. А тут, под Линукс с полной оптимизацией стало падать. Причем вызовы то выполняются, только криво. Может быть this кривой, может таки стек очищается. Дизассемблировать, чтоб точно определить весьма сложно. Я хочу понять, проблема в поем недопонимании языка, или таки в ошибках компилятора.
Здравствуйте, Lis182, Вы писали:
L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.
Напиши отдельный метод, который закрывает файл и сбрасывает handle, и пусть деструктор его вызывает, и твоя функция, которой надо закрыть файл, тоже.
L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.
Вызывать деструктор явно — это очень плохо.
Нужен метод Close(), который закрывает hFile если он не INVALID_HANDLE_VALUE и сбрасывает hFile в INVALID_HANDLE_VALUE.
А еще лучше применить SmartHandle (аналог SmaprPointer), тут на форуме проходил такой велосипед много лет назад.
Модератор-националист Kerk преследует оппонентов по политическим мотивам.
L>>Я хочу понять, проблема в поем недопонимании языка, или таки в ошибках компилятора.
Pzz>А чем тебе поможет это понимание?
Ну во первых, оценю степень глючности разных компиляторов. Во вторых, если всетки это компилятор, похвастаюсь начальству, которое очень скептически реагировало на мои заявления, что я (и чел, который переписывал мой код) в общем-то не виноваты в проблеме.
СR>Вызывать деструктор явно — это очень плохо. СR>Нужен метод Close(), который закрывает hFile если он не INVALID_HANDLE_VALUE и сбрасывает hFile в INVALID_HANDLE_VALUE. СR>А еще лучше применить SmartHandle (аналог SmaprPointer), тут на форуме проходил такой велосипед много лет назад.
Спасибо, конечно, я примерно так и сделал. Только я говорю не про красоту кода, а стандарты. Как я понимаю, такой мой вызов деструктора работать должен, и если он не работает, это проблема компилятора.
На счет пустого присваивания — то это зря. Например, если так:
struct SMyStruct
{
int val;
~SMyStruct()
{
val = 123;
}
};
SMyStruct obj;
obj.~SMyStruct()
int a = 456 + obj.val;
// и т.д.
Если я правильно понимаю язык, то как не оптимизируй, функция должна быть вызвана. А в примере с файлом, она таки 2раза вызывается, и поле используется в условии.
Здравствуйте, Lis182, Вы писали:
L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.
Вообще в таких случаях надо либо добавить метод close(), либо использовать Optional чтоб явно чистить.
σ>>Первое. Компилятор правомерно выкидывает бесполезное присваивание `hFile = INVALID_HANDLE_VALUE`.
L>На счет пустого присваивания — то это зря. Например, если так:
L>
L>struct SMyStruct
L>{
L> int val;
L> ~SMyStruct()
L> {
L> val = 123;
L> }
L>};
L>SMyStruct obj;
L>obj.~SMyStruct()
L>int a = 456 + obj.val;
L>// и т.д.
L>
L>Если я правильно понимаю язык, то как не оптимизируй, функция должна быть вызвана. А в примере с файлом, она таки 2раза вызывается
A>>Тогда ещё раз, тоже самое, но другими словами. A>>Компиляция выполняется в результате многократного числа проходов по коду. На каждом из проходов что-то добавляется, что-то упрощается или выкидывается. Каждый проход использует результаты предыдущих. А теперь добавляем в код руками то самое, что могло или должно было быть добавлено в результате одного из таких проходов. Как именно тогда будет действовать компилятор?
L>Спасибо за ответ Но деструктор это функция, и как любая функция должен вызываться если это указано в коде. У меня только сомнения в обязательности хранения объекта до выхода из области видимости. У меня этот код отлично работал с разными компиляторами много лет. А тут, под Линукс с полной оптимизацией стало падать. Причем вызовы то выполняются, только криво. Может быть this кривой, может таки стек очищается. Дизассемблировать, чтоб точно определить весьма сложно. Я хочу понять, проблема в поем недопонимании языка, или таки в ошибках компилятора.
Проблема в хреновом знании С++. Повторный вызов деструктора для экземпляра класса является UB и его использование однозначно говорит об уровне познаний аффтара кода.
Есть два варианта вызова деструктора, если используется тот, который «Virtual call», то для его корректного выполнения требуется кое-что дополнительное, в сравнении с «non-virtual call».
Да ничего компилятор не выбрасывает и выбросить не может. Тот же clang 11 послушно будет генерировать два вызова деструктора.
Однако, учти, что если компилятор недайбог увидит что "CloseHandle" не имеет сайд-эффектов — то весь твой пример скомпилируется в пустой код, и это будет тоже правильно.