Я получаю ошибку сегментации, которую не могу понять при очистке вектора. Удаление доступа к массиву (3-я строка снизу) устраняет сбой, даже если сбой не в этой строке.
Код ниже — это наименьшее воспроизведение, которое я закончил, и оно извлечено из более крупной кодовой базы (я подтвердил, что он дает сбой в dtor, а не при записи в массив).
#include <array>
#include <cstddef>
#include <iostream>
#include <memory>
#include <vector>
class Deletable {
public:
virtual ~Deletable() = default;
};
template <typename V> class DeletableHandle : public Deletable {
public:
virtual V *get() = 0;
virtual const V *get() const = 0;
};
template <typename V> class DeletableHolder final : public DeletableHandle<V> {
public:
V *get() override { return &value_; }
const V *get() const override { return &value_; }
private:
V value_;
};
class InferenceContext2 {
public:
template <size_t LI> auto *LayerScratchSpace() {
using Area = std::array<int, 4>;
while (buffers_.size() <= LI) {
buffers_.emplace_back(nullptr);
}
if (buffers_[LI] == nullptr) {
buffers_[LI] = std::make_unique<DeletableHolder<Area>>();
}
return reinterpret_cast<Area *>(buffers_[LI].get());
}
private:
std::vector<std::unique_ptr<Deletable>> buffers_;
};
int main() {
InferenceContext2 ctx;
auto area = ctx.LayerScratchSpace<0>();
std::cout << area->size() << " " << (reinterpret_cast<intptr_t>(&area) % 128)
<< " " << alignof(std::remove_pointer_t<decltype(area)>) << "\n";
// Commenting out the line below removes the crash
(*area)[0] = 1;
return 0;
}
В моем более крупном проекте у меня есть ASAN, и вот что он говорит (трассировка стека другая):
AddressSanitizer:DEADLYSIGNAL
=================================================================
==12==ERROR: AddressSanitizer: SEGV on unknown address 0x55f800000009 (pc 0x55f85eadc1c1 bp 0x7ffd295108b0 sp 0x7ffd29510890 T0)
==12==The signal is caused by a READ memory access.
#0 0x55f85eadc1c1 in std::default_delete<uchen::memory::Deletable>::operator()(uchen::memory::Deletable*) const /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:85:2
#1 0x55f85eadc0fb in std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>::~unique_ptr() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:361:4
#2 0x55f85eadc064 in void std::destroy_at<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:88:15
#3 0x55f85eadd214 in void std::_Destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:149:7
#4 0x55f85eadd1e6 in void std::_Destroy_aux<false>::__destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:163:6
#5 0x55f85eadd1ac in void std::_Destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:195:7
#6 0x55f85eadd0d0 in void std::_Destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::allocator<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>&) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:848:7
#7 0x55f85eadd09e in std::vector<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>, std::allocator<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>>::~vector() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:680:2
#8 0x55f85ead8d24 in uchen::(anonymous namespace)::InferenceContext2::~InferenceContext2() /proc/self/cwd/test/arena.test.cc:71:7
Воспроизводится с помощью GCC и Clang. Стройте с --std=c++20. Версии компилятора:
eugene-aurora:~/code$ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
eugene-aurora:~/code$ clang++ --version
Ubuntu clang version 15.0.7
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
buffers_[LI].get() — это Deletable*, reinterpret_cast<Area *>(buffers_[LI].get()); — неопределенное поведение с полиморфным типом Deletable. На практике (*area)[0] = 1; перезаписывает указатель виртуальной таблицы DeletableHolder, и ваша программа аварийно завершает работу при доступе к ней.





buffers_[LI].get() возвращает адрес DeletableHolder.static_cast<DeletableHolder*>(buffers_[LI].get())->get() вызывает get() на этом DeletableHolder, и это действительно Area *.
Обработав первые несколько байтов вашего DeletableHolder как Area и записав в первый элемент этого «массива», вы уничтожили vtable, содержащую адрес виртуального деструктора.
Подумайте дважды, прежде чем отключать предупреждения компилятора с помощью приведения типов в стиле C или reinterpret_cast. static_cast предупредил бы вас об этом.
Большое спасибо! Потратил на это столько времени.
Вероятно, он предупредил меня, поэтому я переключился на reinterpret_cast и потратил все свободное время на отладку в течение двух дней...
По моему мнению, вы перепутали использование
getunique_ptr сgetDeletableHandle . Какreinterpret_cast<Area *>(buffers_[LI].get())может работать, если он преобразует Deletable в массив? Оба типа совершенно не связаны между собой.