【问题标题】:Can I force std::vector to leave a memory leak?我可以强制 std::vector 留下内存泄漏吗?
【发布时间】:2019-05-14 10:08:52
【问题描述】:

我可以强制 std::vector 在向量超出范围后不释放其内存吗?

例如,如果我有

int* foo() {
    std::vector<int> v(10,1); // trivial vector
    return &v[0];
}

int main()
{
    int* bar = foo();
    std::cout << bar[5] << std::endl;
}

无法保证这些值仍可在此处访问。

我目前只是这样做

int* foo() {
  std::vector<int> v(10,1);
  int* w = new int[10];
  for (int i=0; i<10; i++) {
    w[i] = v[i];
  }
  return w;
}

但是重新填充一个全新的数组有点浪费。有没有办法强制 std::vector 不删除它的数组?

注意:我没有返回向量本身,因为我正在使用 SWIG 将 c++ 与 python 连接,而ARG_OUTVIEW_ARRAY 需要一个原始指针,实际上是故意的内存泄漏。然而,我仍然希望能够在构建数据本身时利用矢量特征。

【问题讨论】:

  • 为什么 foo 不返回 int ?最终有人必须存储价值......
  • @TheZhengmeister:为什么不把std::vector&lt;int&gt; v = new std::vector&lt;int&gt;(10,1); 换成foo
  • 为什么不在 SWIG 中使用原始数组?这是可能的。
  • @TheZhengmeister 不,这是个糟糕的主意。如果你返回&amp;(*v)[0],即使你释放了它的内部数组,你也会泄漏向量本身。
  • %include &lt;std_vector.i&gt; then %template(IntVector) std::vector&lt;int&gt;; 将允许传递和返回向量。

标签: c++ swig


【解决方案1】:

这是可能的,但你永远不应该这样做。强制向量离开内存泄漏是一个糟糕的主意,如果你需要这样的东西,那么你需要重新考虑你的设计。 std::vector 是一种资源管理类型,其主要目标之一是确保我们没有泄漏。永远不要试图打破它。

现在,回答您的具体问题:std::vector 将分配器类型作为第二个模板参数,默认为 std::allocator&lt;T&gt;。现在您可以编写一个不会释放任何内存的自定义分配器并将其与您的向量一起使用。编写自定义分配器并不是一件很简单的工作,所以我不会在这里描述(但你可以谷歌找到教程)。

如果你真的想使用自定义分配器,那么你必须确保你的向量永远不会触发增长操作。因为在容量增长期间,向量会将数据移动/复制到新位置并使用分配器释放旧内存。如果您使用泄漏的分配器,那么在增长过程中,您不仅会保留最终数据,还会保留我确定您不想保留的旧记忆。因此,请确保您创建的向量具有全部容量。

【讨论】:

    【解决方案2】:

    vector 旨在防止泄漏。

    但是,如果您想朝自己的脚开枪,那是可能的。以下是防止向量释放其内部数组的方法:

    int *foo()
    {
        std::vector<int> v(10,1);
    
        int *ret = v.data();
        new (&v) std::vector<int>; // Replace `v` with an empty vector. Old storage is leaked.
        return ret;
    }
    

    正如其他答案所说,你永远不应该这样做。

    【讨论】:

    • 这比使用自定义分配器更容易和更干净。谢谢。
    • 我很确定那是 UB,但应该可以在常见的运行时上工作......
    • @Quentin 如果由于某种原因v[0] 不在分配存储的开头,则稍后在delete[] 指针时可能会获得UB。我认为placement-new 本身在这里定义良好,因为允许通过重用存储来销毁对象。此外,如果vector 管理除了单个堆内存块之外的其他资源,我们会泄漏它。听起来对吗?
    • @HolyBlackCat 删除应该使用与std::vector 内部使用的完全相同的形式,分配器和所有——在普通情况下可能会匹配delete[],但不能保证。我不确定通过placement-new缩短非平凡可破坏对象的生命周期是否定义明确。
    【解决方案3】:

    没有。

    向量没有实现内存泄漏,并且接口不提供创建方法。

    你不能“窃取”内存(从向量中删除它的所有权),这可能有点可惜。

    抱歉,您将不得不复制(就像您现在所做的那样),或者不使用矢量。

    【讨论】:

    • 使用不释放内存的自定义分配器怎么样?我知道这是一个糟糕的想法,但应该可以实施。
    • 我不会招待这种骇客的!
    • 大声笑。是的,应该避免这种黑客行为,但在技术上是可行的。至少可能只是为了好玩:-)
    【解决方案4】:

    这是一个坏主意,但可以通过创建一个不会像其他答案中所说的那样解除分配的自定义分配器。

    例如:(样板大多来自cppref

    #include <cstdlib>
    #include <new>
    #include <vector>
    
    template <typename T>
    struct LeakingAllocator 
    {
      using value_type = T;
      LeakingAllocator() = default;
    
      template <typename U> constexpr LeakingAllocator(const LeakingAllocator<U>&) noexcept {}
    
      T* allocate(std::size_t n) 
      {
        if(n > std::size_t(-1) / sizeof(T)) throw std::bad_alloc(); // check for overflow
    
        if(auto p = static_cast<T*>(std::malloc(n*sizeof(T)))) return p; // return p if malloc returns a valid object
        throw std::bad_alloc(); // otherwise just throw.
      }
    
      void deallocate(T* p, std::size_t) noexcept { /*leak intentionally*/ }
    };
    
    
    template <typename T, typename U>
    bool operator==(const LeakingAllocator<T>&, const LeakingAllocator<U>&) { return true; }
    template <typename T, typename U>
    bool operator!=(const LeakingAllocator<T>&, const LeakingAllocator<U>&) { return false; }
    
    template <typename T>
    using LeakingVector = std::vector<T, LeakingAllocator<T>>;
    

    然后像这样的代码

    int* ret()
    {
        LeakingVector<int> a;
        a.resize(10);
        return &a[0];
    }
    
    int main()
    {
        auto ptr = ret();
        *ptr = 10;
        std::cout << *ptr;
    }
    

    变为有效。

    【讨论】:

      【解决方案5】:

      不确定,但是,是的。

      你可以创建一个客户allocator,在解除分配=>泄漏时什么都不做

      或者你可以在堆上创建你的vector,这样它无论如何都会泄漏。

      int* foo() {
          std::vector<int>* v = new std::vector<int>(10,1); 
          return &((*v)[0]);
          // no delete
      }
      
      int main()
      {
          int* bar = foo();
          std::cout << bar[5] << std::endl;
      }
      

      【讨论】:

      • 第二个选项会造成实际泄漏(而不是从vector 删除缓冲区的所有权,这正是 OP 似乎想要的)。
      • 这会导致意外泄漏超过所需内存量的风险。此外,一旦你完成它,你没有最终释放内存的策略。这是一个糟糕的主意。
      • 当然这是一个糟糕的主意,但是OP想要制造泄漏,我帮助他这样做^^
      • 问题标题具有误导性。 OP 似乎想从向量中重用缓冲区,而不是泄漏它。
      • @TheZhengmeister 此处显示的示例除了数组外还会泄漏vector 对象本身。不要使用它。
      【解决方案6】:

      没有。

      而你做错了。 返回向量,以便计算出生命周期:

      编写你自己的特殊 Python 内存向量 class,类似于(最粗略):

      template <typename T>
      class python_vector
      {
          T* buffer_;
      public:
          python_vector(size_t n, const T& value) : buffer_{new T(n)}
          {}
          // copy, assignment, operator[](), *etc*
          ~python_vector()
          {
              // DO NOTHING!
          }
      }
      
      python_vector<int> foo() {      
          python_vector<int> v(10,1);
          // process v
          return v;
      }
      
      int main()
      {
          python_vector<int> bar = foo();  // copy allusion will build only one python_vector here
          std::cout << bar[5] << std::endl;
      }
      

      【讨论】:

      • OP 提到了为什么他没有在问题末尾的注释中返回向量。
      • @P.W 但他希望vector 数据的寿命超过返回值?设计是错误的。
      • 是的,我同意你的看法。
      • 我需要数据的寿命超过返回,因为 SWIG 需要“内存泄漏”,以便 python 可以控制内存,然后在 c++ 的生命周期后释放它。
      • @TheZhengmeister 那么vector 是错误的工具。以您可以控制的方式分配和管理内存,这样它就不会在 C++ 生命周期内被释放。
      【解决方案7】:

      在 C++ 中你很可能会这样写:

      auto foo()
      {
          std::vector<int> v(10,1); // trivial vector
          return v;
      }
      
      int main()
      {
          const auto bar = foo();
          std::cout << bar[5] << std::endl;
      }
      

      【讨论】:

      • 我需要一个原始指针,因为我使用 SWIG 将 c++ 与 python 接口,在 c++ 超出范围后将内存管理转移到 python。它必须是“泄漏”,否则操作系统可能只会覆盖分配的内存。
      • 我不认为,Python 能够正确管理 C++ 运行时分配的内存。也许你应该从 Python 中获取内存并将向量数据复制到其中。
      猜你喜欢
      • 1970-01-01
      • 2017-02-19
      • 1970-01-01
      • 2016-02-07
      • 1970-01-01
      • 1970-01-01
      • 2016-05-01
      • 2011-10-02
      • 2010-10-28
      相关资源
      最近更新 更多