跳到主要内容

memcpy使用注意事项

· 阅读需 4 分钟
Bruce
Back End Engineer

最近遇到一个偶现的问题,moon提供了异步pgsql client driver,我们游戏开发中概率性出现查询结果数据协议解析错误,pgsql的协议是很简单的:

  • 1字节表示的消息类型,部分消息类型定义如下:
local MSG_TYPE =
flipped(
{
status = "S",
auth = "R",
backend_key = "K",
ready_for_query = "Z",
query = "Q",
notice = "N",
notification = "A",
password = "p",
row_description = "T",
data_row = "D",
command_complete = "C",
error = "E"
}
)
  • 4字节表示数据长度

这个错误在linux下是概率发生的,windows下没有出现过。发生错误的是D类型,也就是行数据,这部分数据通常比较长。 通过打印日志发现是数据被截断了一部分,这时就怀疑是socket read缓冲区中buffer操作可能有问题,通过分析代码,发现一段代码:

        void prepare(size_t need)
{
if (writeablesize() >= need)
{
return;
}

if (writeablesize() + readpos_ < need + headreserved_)
{
auto required_size = writepos_ + need;
required_size = next_pow2(required_size);
auto tmp = allocator_.allocate(required_size);
if (nullptr != data_)
{
std::memcpy(tmp, data_, writepos_);
allocator_.deallocate(data_, capacity_);
}
data_ = tmp;
capacity_ = required_size;
}
else
{
//如果缓冲区前面部分有空闲空间,并且满足要分配的大小
//则把未读部分移动到前面,组成连续的空闲空间
size_t readable = size();
if (readable != 0)
{
assert(readpos_ >= headreserved_);
std::memcpy(data_ + headreserved_, data_ + readpos_, readable);
}
readpos_ = headreserved_;
writepos_ = readpos_ + readable;
}
}

这里使用了memcpy, 但操作的内存是重叠的,一般以为memcpy从后向前拷贝,重叠也是没问题的,因为常规的实现是按单字节拷贝的。但翻阅标准库文档 发现:

https://en.cppreference.com/w/cpp/string/byte/memcpy

std::memcpy
C++ Strings library Null-terminated byte strings
Defined in header <cstring>

void* memcpy( void* dest, const void* src, std::size_t count );

Copies count bytes from the object pointed to by src to the object pointed to by dest. Both objects are reinterpreted as arrays of unsigned char.

If the objects overlap, the behavior is undefined.(注意这里: 重叠时,是未定义行为)

If either dest or src is an invalid or null pointer, the behavior is undefined, even if count is zero.

If the objects are potentially-overlapping or not TriviallyCopyable, the behavior of memcpy is not specified and may be undefined

一般标准库中的 memcpy 实现是被优化过后的,自己写的性能很难超过它,一种优化方式就是单次拷贝不止一个字节。可能正是这个原因,造成了从后向前拷贝也会出问题。换成memmove应该就可以解决这个问题。

最后还有一个疑问,windows下为何没有出现过呢,因为这个问题在我们的测试环境下概率是很高的,大部分开发人员都是在windows环境下的,通过查询资料发现:

https://www.zhihu.com/question/264505093

Windows 的 C 运行库(msvcrt)里,memcpy 实际上是 memmove 的 alias。微软虽然在 MSDN 里还是按照标准的说法描述 memcpy 和 memmove,但它的 C 库实际上二十年前就不区分了。