令人困惑的行为,malloc和free(),与libuv

时间:2013-03-29 21:27:47

标签: c malloc free libuv

使用示例代码了解libuv我遇到了一个我不明白的副作用。代码使用malloc()来获取内存,以便从网络上的客户端存储数据,然后发回相同的数据,只需回声即可。然后它使用free来释放内存。这通过回调循环一遍又一遍地重复。获取内存的代码行是:

uv_write_t *req = (uv_write_t *) malloc(sizeof(uv_write_t));

释放内存的行是:

free((char*) req->data);
free(req);

但是,如果您输入一个长字符串,例如“街上有什么字?”要回显然后放入较短的字符串,比较旧的字符串的“Hi”片段将在较短的字符串被回显之后重新出现。例如输出可以是这样的:

街上有什么字? 嗨 嗨 你好 你好 他在街上说话了吗?

由于内存被释放,我不确定为什么较旧的片段会显示备份。我对这个问题的想法是,有一些我不了解的关于malloc和free()的东西,或者库中有一个错误,它确定了传入数据所需的大小,并且在使用更长的字符串后我得到了垃圾作为内存块的一部分,是大的。如果是这种情况,那么事实上它是我之前输入的一个片段只是偶然事件。这是可能的原因,还是我错过了什么?还有其他信息。我应该包括澄清它吗?

2 个答案:

答案 0 :(得分:1)

malloc()的实现会有所不同,但可以安全地假设对malloc()的调用可以返回指向以前free() - ed内存块的指针,并且返回的内存不会被清零。换句话说,它完全正常的malloc()给我们一个指向包含以前初始化数据的数据的指针。

那就是说,我怀疑这里的根问题是一个未终止的字符串,这可能是你序列化字符串的一种神器。例如,如果您只是从客户端编写strlen(str)字节,则不会写入NULL。因此,当服务器收到消息时,它将具有未终止的字符串。如果您打算如何传递字符串并计划将其视为普通的以空字符结尾的字符串,则服务器需要将数据复制到足够大的缓冲区中以容纳字符串以及额外的NULL字符。

那么为什么你会看到过去消息的片段?可能是运气不好。如果这是一个非常简单的应用程序,malloc()很可能返回与前一个请求重叠的一块内存。

那么为什么我得到这么干净的输出,我不应该看到大量的乱码数据,或者我的字符串操作走向无限的段错误?再一次,愚蠢的运气。请记住,当内核首次为您的应用程序提供一页内存时,它将首先将页面清零(这是出于安全原因)。因此,即使您可能没有终止字符串,您的字符串所在的堆内存页面可能处于相对原始的清零状态。

答案 1 :(得分:1)

uv_write_t * req 要发送或接收的数据。它就像是写请求的句柄。

req->数据都不是。这是指向您的任意私有数据的指针。例如,如果您想要传递与连接相关的一些数据,则可以使用它。

实际有效负载数据通过写缓冲区(uv_buf_t)发送,并接收到在提供读请求时分配的缓冲区。这就是read函数需要alloc参数的原因。稍后该缓冲区将传递给读回调。

释放req->数据假设'data'指向一些私有数据,通常是一个结构,由malloc'd(由你)。

根据经验,套接字由uv_xxx_t表示,同时读写使用“请求”结构。 编写服务器(一个典型的uv用例),不知道会有多少连接,因此一切都是动态分配的。

为了让您的生活更轻松,您可以考虑成对(开/关或开始/完成)。因此,当接受新连接时,您启动一​​个循环并分配客户端。关闭该连接时,将其释放。写入时,您将分配请求以及有效负载数据缓冲区。完成写作后,你可以释放它们。在读取时,您分配一个读取请求,并在完成读取(并复制了有效负载数据)后在场景后面(通过alloc回调)分配有效负载数据缓冲区,您可以释放它们。

有很多方法可以在没有所有malloc / free对的情况下完成工作(这并不是非常明显的性能)但是对于新手我会同意uv文档;你一定要从malloc / free路线开始。 为了给你一个想法:我预先分配了大约十或十万个连接的所有内容,但这带来了一些管理和诡计,例如伪造在alloc回调中只是分配一个预先分配的缓冲区。

如果被要求猜测我建议避免使用malloc / free只能在任何时间点超过5k - 10k连接的麻烦。