Есть проект, ориентированный на использование C ++ 98 без дополнительных зависимостей, но он должен поддерживать динамически выделяемую память. Умные указатели недоступны, поэтому был добавлен код для ручной очистки. Подход состоит в том, чтобы явно установить переменные в NULL
в CTOR, прочитать некоторые данные, во время которых память может выделяться динамически, перехватить любое возникающее исключение и очистить память по мере необходимости, вручную вызвав DTOR. Это должно в любом случае реализовать освобождение памяти на случай, если все прошло успешно, и было просто усилено мерами безопасности, чтобы проверить, была ли выделена память вообще или нет.
Ниже приводится наиболее актуальный доступный код на этот вопрос:
default_endian_expr_exception_t::doc_t::doc_t(kaitai::kstream* p__io, default_endian_expr_exception_t* p__parent, default_endian_expr_exception_t* p__root) : kaitai::kstruct(p__io) {
m__parent = p__parent;
m__root = p__root;
m_main = 0;
try {
_read();
} catch(...) {
this->~doc_t();
throw;
}
}
void default_endian_expr_exception_t::doc_t::_read() {
m_indicator = m__io->read_bytes(2);
m_main = new main_obj_t(m__io, this, m__root);
}
default_endian_expr_exception_t::doc_t::~doc_t() {
if (m_main) {
delete m_main; m_main = 0;
}
}
Самая важная часть заголовка следующая:
class doc_t : public kaitai::kstruct {
public:
doc_t(kaitai::kstream* p__io, default_endian_expr_exception_t* p__parent = 0, default_endian_expr_exception_t* p__root = 0);
private:
void _read();
public:
~doc_t();
private:
std::string m_indicator;
main_obj_t* m_main;
default_endian_expr_exception_t* m__root;
default_endian_expr_exception_t* m__parent;
};
Код протестирован в трех разных средах , clang3.5_linux
, clang7.3_osx
и msvc141_windows_x64
, чтобы явно генерировать исключения при чтении данных и при утечке памяти в этих условиях. Проблема в том, что это вызывает SIGABRT
на CLANG 3.5 только для Linux. Наиболее интересными кадрами стека являются следующие:
<frame>
<ip>0x577636E</ip>
<obj>/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19</obj>
<fn>std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()</fn>
</frame>
<frame>
<ip>0x5ECFB4</ip>
<obj>/home/travis/build/kaitai-io/ci_targets/compiled/cpp_stl_98/bin/ks_tests</obj>
<fn>default_endian_expr_exception_t::doc_t::doc_t(kaitai::kstream*, default_endian_expr_exception_t*, default_endian_expr_exception_t*)</fn>
<dir>/home/travis/build/kaitai-io/ci_targets/tests/compiled/cpp_stl_98</dir>
<file>default_endian_expr_exception.cpp</file>
<line>51</line>
</frame>
[...]
<frame>
<ip>0x577636E</ip>
<obj>/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19</obj>
<fn>std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()</fn>
</frame>
<frame>
<ip>0x5ED17E</ip>
<obj>/home/travis/build/kaitai-io/ci_targets/compiled/cpp_stl_98/bin/ks_tests</obj>
<fn>default_endian_expr_exception_t::doc_t::~doc_t()</fn>
<dir>/home/travis/build/kaitai-io/ci_targets/tests/compiled/cpp_stl_98</dir>
<file>default_endian_expr_exception.cpp</file>
<line>62</line>
</frame>
Строки 51 и 62 являются последними строками CTOR и DTOR, как указано выше, так что на самом деле закрывающие скобки. Похоже, что какой-то добавленный компилятором код просто пытается освободить поддерживаемый std::string
два раза, один раз в DTOR и еще раз в CTOR, скорее всего, только при генерации исключения.
Это анализ вообще верен?
И если да, то это ожидаемое поведение C ++ в целом или только этого конкретного компилятора? Интересно, потому что другие компиляторы не SIGABRT
, хотя код для всех одинаковый. Означает ли это, что разные компиляторы по-разному очищают не-указатели, такие как std::string
? Как узнать, как ведет себя каждый компилятор?
Глядя на то, что говорит C ++ - стандарт , я ожидал, что std::string
будет освобожден только CTOR из-за исключения:
C ++ 11 15.2 Конструкторы и деструкторы (2)
Объект любой продолжительности хранения, инициализация или уничтожение которого завершается исключением, будет иметь деструкторы выполняется для всех его полностью построенных подобъектов (за исключением вариантов членов объединенного класса), то есть для подобъектов, для которых главный конструктор (12.6.2) завершил выполнение, а деструктор еще не начал выполнение.
В данном случае уничтожение НЕ завершается исключением, а только построением. Но поскольку DTOR - это DTOR, он также предназначен для автоматической очистки? И если да, то в целом со всеми компиляторами или только с этим?
Надежен ли вообще вызов DTOR вручную?
Согласно моим исследованиям, вызов DTOR вручную не должен быть слишком плохим . Это неправильное выражение, и это большой отказ из-за того, что я вижу прямо сейчас? У меня сложилось впечатление, что если DTOR вызывается вручную, он просто должен быть совместим, чтобы называться таким образом. Какое из вышеперечисленных должно быть из моего понимания Он не работает только из-за автоматически сгенерированного кода компилятором, о котором я не знал.
Как это исправить?
Вместо того, чтобы вызывать DTOR вручную и запускать автоматически сгенерированный код, один следует просто использовать специальную функцию cleanUp
, освобождающую память и устанавливающую указатели на NULL
? Должно быть безопасно вызывать это в CTOR в случае исключения и всегда в DTOR, правильно? Или есть способ продолжать вызывать DTOR совместимым образом для всех компиляторов?
Спасибо!