【问题标题】:How should memory be freed after an exception is thrown in C++?C++中抛出异常后应该如何释放内存?
【发布时间】:2013-07-29 20:26:02
【问题描述】:

如果这个问题是重复的,我深表歉意 - 我搜索了一段时间,但我的 Google-fu 可能无法满足要求。

我正在修改一个调用 C 库的 C++ 程序。 C 库分配了一堆内存(使用malloc()),C++ 程序使用它然后释放它。问题是 C++ 程序可以在执行过程中抛出异常,导致分配的内存永远不会被释放。

作为一个(相当做作的)示例:

/* old_library.c */
char *allocate_lots() {
    char *mem = (char *)malloc(1024);
    return mem;
}

/* my_prog.cpp */
void my_class::my_func () {
    char *mem = allocate_lots();
    bool problem = use(mem);
    if (problem)
        throw my_exception("Oh noes! This will be caught higher up");
    free(mem);  // Never gets called if problem is true
}

我的问题是:我应该如何处理这个问题?我的第一个想法是将整个东西包装在一个 try/catch 块中,并在 catch 中检查并释放内存并重新抛出异常,但这对我来说似乎很不优雅和笨重(如果我不能很好地工作想要实际上捕获一个异常)。有没有更好的方法?

编辑:我可能应该提到我们正在使用 g++ 4.2.2,早在 2007 年引入 std::unique_ptr 之前。将其归结为企业惯性。

【问题讨论】:

  • 为什么不能在抛出异常之前释放内存?
  • 使用 RAII,问题解决了。

标签: exception memory malloc c++03


【解决方案1】:

std::unique_ptr 与可免费调用的自定义删除器一起使用:

class free_mem {
public:
    void operator()(char *mem) { free(mem); }
};

void my_class::my_func() {
    std::unique_ptr<char, free_mem> mem = allocate_lots();

【讨论】:

  • 这可能是正确的答案,除了我们使用的是旧版本的 g++(参见上面的编辑)。
  • 编写你自己的 unique_ptr 替换,然后向你的老板解释为什么你花了 2 天时间调试一个基本的语言工具,如果他们允许升级,你会免费获得。重复此过程,直到他们开始权衡使用过时工具所浪费的时间与升级所花费的时间。
【解决方案2】:

您应该确保在释放内存之前不要抛出 - 或者使用合适的智能指针结构来存储 mem,这样当 throw 发生时,堆栈就会展开,mem 被释放。

【讨论】:

    【解决方案3】:

    包住那个无赖:

    struct malloc_deleter {
      template <typename T>
      void operator () (T* p) const {
        free(p);
      }
    };
    
    void my_class::my_func () {
        std::unique_ptr<char[],malloc_deleter> mem{allocate_lots()};
        bool problem = use(mem.get());
        if (problem)
            throw my_exception("Oh noes! This will be caught higher up");
    }
    

    【讨论】:

    • 嗯。能解释一下char[]的用法吗?
    • unique_ptr&lt;T[]&gt; 是重载operator [] 而不是operator -&gt; 并默认使用delete[] 的部分特化(这里显然不重要)。
    • 那么,我永远对此感到困惑。我以为shared_ptr 有。但显然,他们忘了为shared_ptr添加相同的内容
    • 正确。 boost::shared_ptr 有一个 T[] 特化,但它是在 TR1 中标准吞并 shared_ptr 之后添加的。 (见this thread)。
    • 这可能是正确的答案,除了我们使用的是旧版本的 g++(参见上面的编辑)。
    【解决方案4】:

    由于您使用的是没有 unique_ptr 的旧编译器版本,因此您可以自己编写 RAII 包装器:

    class ResourceWrapper {
    public:
        ResourceWrapper(char* ptr) : m_ptr(ptr) {}
        ~ResourceWrapper() { free(m_ptr); }
        // whatever getters suit you, at the very least:
        char* get() const { return m_ptr; }
    private:
        char* const m_ptr;
    };
    
    void my_class::my_func () {
        ResourceWrapper mem(allocate_lots());
        bool problem = use(mem.get());
        if (problem)
            throw my_exception("Oh noes! This will be caught higher up");
    }
    

    只要确保 允许复制/分配,即使是隐式的(这就是我创建 m_ptr const 的原因),否则你会冒着双重释放你的记忆的风险(“移动”语义à la auto_ptr 最好避免使用,除非您绝对需要它)。

    【讨论】:

      【解决方案5】:

      由于您不能使用std::unique_ptr,您可以创建自己的删除器类,以 RAII 方式控制指针的生命周期。为了简单起见,这个例子没有包装实际的指针,而是存在于它旁边;更安全的方法是创建一个真正的智能指针类。

      class AutoFree
      {
      public:
          AutoFree(void* p) : m_p(p)
          {
          }
          ~AutoFree()
          {
              free(m_p);
          }
      private:
          void* m_p;
      };
      
      void my_class::my_func () {
          char *mem = allocate_lots();
          AutoFree mem_free(mem);
          bool problem = use(mem);
          if (problem)
              throw my_exception("Oh noes! This will be caught higher up");
      }
      

      【讨论】:

        【解决方案6】:

        有什么理由不简单地释放 if 子句中的内存吗?

        if (problem) {
            free (mem);
            throw my_exception ("Drat!");
        }
        

        【讨论】:

        • 代码重复,尽管在这种特定情况下,free 确实可以完全移动到条件之前。从长远来看,不知道这里发生了什么的人可能会插入另一个 throwreturn 或调用另一个可能在不处理脆弱资源的情况下抛出的函数。 RAII 可以防止下一个愚蠢的人。
        【解决方案7】:

        使用 unique_ptr:http://coliru.stacked-crooked.com/view?id=cd3f0fc64d99cc07a2350e2ff9686500-542192d2d8aca3c820c7acc656fa0c68

        #include <stdexcept>
        #include <iostream>
        
        #include <memory>
        
        /* old_library.c */
        char *allocate_lots()
        {
            return static_cast<char*>(malloc(1024));
        }
        
        struct my_exception : virtual std::exception {
            const char* const msg;
            my_exception(const char* const msg) : msg(msg) {}
            const char* what() const noexcept { return msg; }
        };
        
        struct my_class
        {
            struct Free { void operator() (char* p) const { free(p); } };
            /* my_prog.cpp */
            void my_func()
            {
                std::unique_ptr<char, Free> mem;
        
                mem.reset(allocate_lots());
                bool problem = use(mem.get());
        
                if(problem)
                {
                    throw my_exception("Oh noes! This will be caught higher up");
                }
            }
        
            static bool use(char*) { return true; }
        };
        
        int main()
        {
            my_class prog;
            prog.my_func();
        }
        

        【讨论】:

        猜你喜欢
        • 2018-12-31
        • 2013-06-18
        • 2014-12-15
        • 2014-04-03
        • 2010-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-10
        相关资源
        最近更新 更多