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

Я получаю ошибку сегментации, которую не могу понять при очистке вектора. Удаление доступа к массиву (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

По моему мнению, вы перепутали использование get unique_ptr с get DeletableHandle . Как reinterpret_cast<Area *>(buffers_[LI].get()) может работать, если он преобразует Deletable в массив? Оба типа совершенно не связаны между собой.

rafix07 22.05.2024 17:01
buffers_[LI].get() — это Deletable*, reinterpret_cast<Area *>(buffers_[LI].get()); — неопределенное поведение с полиморфным типом Deletable. На практике (*area)[0] = 1; перезаписывает указатель виртуальной таблицы DeletableHolder, и ваша программа аварийно завершает работу при доступе к ней.
3CxEZiVlQ 22.05.2024 17:15
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

buffers_[LI].get() возвращает адрес DeletableHolder.
static_cast<DeletableHolder*>(buffers_[LI].get())->get() вызывает get() на этом DeletableHolder, и это действительно Area *.

Обработав первые несколько байтов вашего DeletableHolder как Area и записав в первый элемент этого «массива», вы уничтожили vtable, содержащую адрес виртуального деструктора.

Подумайте дважды, прежде чем отключать предупреждения компилятора с помощью приведения типов в стиле C или reinterpret_cast. static_cast предупредил бы вас об этом.

Большое спасибо! Потратил на это столько времени.

Eugene 22.05.2024 18:04

Вероятно, он предупредил меня, поэтому я переключился на reinterpret_cast и потратил все свободное время на отладку в течение двух дней...

Eugene 22.05.2024 18:25

Другие вопросы по теме

Есть ли способ извлечь поиск из регулярного выражения С++? (вопрос не о регулярных выражениях, а #include <regex>)
Future не возвращает ссылку на значение в C++
ASAN показывает утечку памяти через конфигурацию отладки CMAKE, но не показывает утечку памяти через конфигурацию выпуска CMAKE. Почему?
Ошибка при вызове: слишком много объектов указанного типа (PJ_ETOOMANY) [status=70010]
Флаг версии g++ неправильно переключает версию C++
Почему код вылетает при вызове std::string::begin()?
C++11 можно ли создать интерфейс с функцией, которая позволяет использовать различные типы возврата T или const T&?
Почему #include RcppArmadillo заставляет R компилировать код Rcpp с C++11 вместо C++17 в Ubuntu 22.04?
Дерево с std::unique_ptr. Ссылку нельзя привязать к разыменованному нулевому указателю в четко определенном коде C++
Ошибка: `неопределенная ссылка` на функцию с массивом и указателем функции в качестве параметров