【发布时间】: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