寻求无大临时缓冲区的memmove2实现方案及问题命名
函数需求
需要实现如下签名的函数:
void *memmove2 ( void *destination, const void *source1, size_t num1, const void *source2, size_t num2 );
该函数需连续将source1的num1字节、source2的num2字节复制到destination中,且所有参数对应的内存区域可任意重叠(目标与任一源、源之间或三者均重叠),所有复制操作必须基于内存的初始状态执行。
示例场景
char test[10] = "123456789"; memmove2(test, test + 1, 3, test + 2, 4);
执行后应得到结果"234345689"(前3字节来自初始的test+1,后4字节来自初始的test+2)。
错误方案:连续调用memmove
如果连续调用标准memmove无法达成目标:
char test[10] = "123456789"; memmove(test, test + 1, 3); memmove(test + 3, test + 2, 4);
执行后会得到"234445689"——因为第一次memmove会修改test[0]-test[2],导致第二次memmove读取的test+2已经是被修改后的值,而非初始状态。
简单可靠方案:大缓冲区中转
最直接的实现是用与总复制字节数(num1+num2)等大的临时缓冲区,先将两个源的初始数据复制到缓冲区,再统一复制到目标:
char test[10] = "123456789"; char temp[7]; memcpy(temp, test + 1, 3); memcpy(temp + 3, test + 2, 4); memcpy(test, temp, 7);
核心问题与解答
是否存在无需等大缓冲区的通用方案?
无缓冲区(完全原地操作):不可能实现
因为存在无法避免的冲突场景:当目标区域会覆盖后续需要读取的源数据时,原地复制会提前修改源数据,导致无法获取初始状态的字节。比如前面的示例,若不保存初始的test+2区域,第一次复制source1到目标后,test[2]会被修改为初始的test[3],后续读取source2的test[2]时就不是初始值了,必然得到错误结果。
小缓冲区(小于总复制字节数):可行但复杂度极高
可以通过内存区域重叠分析,仅保存那些会被目标提前覆盖的源字节,用更小的缓冲区完成复制,步骤大致如下:
- 分析目标区域(
destination到destination+num1+num2-1)与两个源区域的重叠关系,找出所有会被目标覆盖的源字节(这些字节必须在被覆盖前保存)。 - 将这些需要保护的字节复制到小缓冲区。
- 先复制不会被覆盖的源区域到目标。
- 最后从缓冲区复制被保护的源字节到目标对应位置。
但这种方案需要处理所有可能的重叠组合(目标与src1重叠、目标与src2重叠、src1与src2重叠、三者均重叠等),实现复杂度极高;且在最坏场景下(所有源字节都会被目标覆盖),仍需与总复制字节数等大的缓冲区,无法完全避免内存占用。
该问题的命名
这个问题属于多源重叠内存复制(Multi-source overlapping memory copy),也可称为合并式重叠内存迁移,是系统编程中复杂内存重叠操作的一类场景。
工程选择
虽然小缓冲区方案在部分场景下能减少内存占用,但大缓冲区方案实现简单、逻辑清晰、可靠性高,是工程实践中的首选方案。
内容的提问来源于stack exchange,提问作者FrederikVds

