【问题标题】:How to make this code less memory leak prone?如何使此代码不易发生内存泄漏?
【发布时间】:2013-07-14 02:47:33
【问题描述】:

作为介绍,请注意,我是一名 Java 程序员,仍然习惯于 C++ 中的内存管理问题。

我们有一个基类,用于将对象编码为一串 ASCII 字符。本质上,该类使用stringstream 类成员将不同的数据类型转换为一个长字符串,然后将char* 返回给包含编码对象数据的调用者。

在测试内存泄漏时,我看到我们使用的实现似乎很容易造成内存泄漏,因为用户必须始终记住删除方法的返回值。以下是代码相关部分的摘录:

    char* Msg::encode() 
    {
        // clear any data from the stringstream
        clear();
        if (!onEncode()) {
            return 0;
        }

        // need to convert stringstream to char*
        string encoded = data.str();
                    // need to copy the stringstream to a new char* because 
                    // stringstream.str() goes out of scope when method ends
        char* encoded_copy = copy(encoded);
        return encoded_copy;
    }

    bool Msg::onEncode(void)
    {
        encodeNameValue(TAG(MsgTags::TAG_USERID), companyName);
        encodeNameValue(TAG(MsgTags::TAG_DATE), date);
        return true;
    }

    bool EZXMsg::encodeNameValue(string& name, int value)
    {
        if(empty(value))
        {
            return true;
        }
                    // data is stringstream object
        data << name << TAG_VALUE_SEPARATOR << value << TAG_VALUE_PAIRS_DELIMITER;
        return true;
    }


    char* copy(string& source) {
        char *a=new char[source.length() +1];
        a[source.length()]=0;
        memcpy(a,source.c_str(),source.length());
        return a;
    }

更新

嗯 - 我应该更准确地了解encode() 的结果是如何被消耗的。它被传递给 boost:async_write,并且程序正在崩溃,因为我相信字符串在 async_write 完成之前就超出了范围。似乎我需要将返回的字符串复制到一个类成员,该成员在发送消息(?)的类的生命周期内都是活动的。

这是encode()方法的实际使用方式(我将返回值改为string后):

void iserver_client::send(ezx::iserver::EZXMsg& msg) {
       string encoded = msg.encode();   
       size_t bytes = encoded.length();
       boost::asio::async_write(socket_, boost::asio::buffer(encoded, bytes), boost::bind(&iserver_client::handle_write, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));    
}

看起来这样做的正确方法是维护要异步写入的字符串的队列/列表/向量。如here 所述(以及在 boost chat_client 示例中)。 (但这是一个单独的问题。)

【问题讨论】:

  • 这取决于所需的 API 是什么。最大的问题——返回的char * 应该有效多长时间,谁应该释放与之相关的内存?
  • 为什么不直接从encode() 方法返回string(或者传入对string 的引用)?
  • 不要使用char *作为返回类型;事实上,避免使用原始的char * 字符串并使用std::string。使用RAII(Resource Acquisition Is Initialization)确保异常和正常使用避免泄漏。尽可能避免new;当你不能时要非常小心。
  • @SamGoldberg:我不确定你是否可以假设 boost 正在为你复制缓冲区。我更倾向于相信在系统调用期间唯一的复制是从用户空间到内核空间。因此,在您收到发送已完成的异步通知之前,可能无法释放指针。
  • @Sam:string 对象只是 char* 的包装。返回时,返回的对象是临时的,所以会使用move构造函数,这只是将内存分配给另一个string对象,而不是复制它。

标签: c++ memory-management


【解决方案1】:

对于这个问题: 在你的复制函数中,你返回一个指向堆内存的指针!所以用户可能会造成内存泄漏,我认为你不能使用这个复制函数,你可以在你的编码函数中这样做:

return data.str();

如果要获取char*,可以使用string的成员函数:c_str(), 就像这样:

string ss("hello world");
const char *p = ss.c_str();

如果您使用堆栈字符串对象,您将不会造成内存泄漏,

【讨论】:

    【解决方案2】:

    您可以只返回一个std::string。反正你有一个:

    string Msg::encode() 
    {
        // clear any data from the stringstream
        clear();
        if (!onEncode()) {
            return string{};
        }
    
        return data.str();
    }
    

    然后调用者看起来像:

    Msg msg;
    msg.userID = 1234;
    send(msg.encode().c_str());
    

    【讨论】:

    • 返回0 到字符串对象会使它崩溃。因为你正在用空指针初始化一个字符串
    • @chris: 我知道... 叹息
    【解决方案3】:

    实现“自动”删除的唯一方法是堆栈变量(在某种程度上)超出范围。事实上,这通常是保证删除的唯一方法,即使在发生异常的情况下也是如此。

    正如其他人提到的std::string 工作得很好,因为char * 归堆栈分配的string 所有,这将删除char *

    这通常不起作用,例如非 char * 类型。

    RAII(Resource Acquisition is Initialization)是处理诸如内存管理、锁获取/释放等问题的有用习语。

    一个好的解决方案是使用 Boost 的scoped_array,如下所示:

    {
      Msg msg;
      msg.userID = 1234;
      scoped_array<char> encoded(msg.encode());
      send(encoded.get());
      // delete[] automatically called on char *
    }
    

    scoped_ptr 对于非数组类型的工作方式类似。

    仅供参考:您应该使用 delete[] encoded 来匹配 new char[source.length() +1]

    【讨论】:

    • 啊 - 感谢有关 delete[] 编码的提示。我不知道。
    【解决方案4】:

    虽然使用 std::string 足以解决您的特定问题,但一般的解决方案是返回 std::unique_ptr 而不是原始指针。

    std::unique_ptr<char[]> Msg::encode() {
        :
        return std::unique_ptr<char[]>(encoded_copy);
    }
    

    然后用户在调用它时会得到一个新的unique_ptr

    auto encoded = msg.encode();
    send(encoded.get());
    

    encoded超出范围并被销毁时,内存将自动释放。

    【讨论】:

    • 使用 unique_ptr 和 boost::shared_ptr 有什么区别?
    • @SamGoldberg:unique_ptr 无法复制,只能移动,因此它是唯一指向该对象的指针。与unique_ptr 相比,std::shared_ptr(与boost::shared_ptr 几乎相同)有一些开销(引用计数),并且不适用于数组,而unique_ptr 有专门的数组版本。
    • 感谢您的解释。这一切都开始变得更有意义了。
    猜你喜欢
    • 2014-07-10
    • 1970-01-01
    • 1970-01-01
    • 2013-08-18
    • 1970-01-01
    • 2012-01-18
    • 2016-02-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多