我正在从头开始编写memcpy,我一直在寻找其他人的实现......我的实现是:
void* memcpy (void *destination, const void *source, size_t num)
{
char *D = (char*)destination;
char *S = (char*)source;
for(int i = 0; i < num; i++)
D[i] = S[i];
return D;
}
我研究过的各种其他来源和参考资料
void* memcpy (void *destination, const void *source, size_t num)
{
char *D = (char*)destination;
char *S = (char*)source;
for(int i = 0; i < num; i++)
{
*D = *S;
D++;
S++;
}
return D;
}
我无法理解差异以及它们是否会产生不同的输出。特别让我困惑的部分是D ++;和S ++;
答案 0 :(得分:2)
现代编译器会将这些优化为相同的代码。它被称为强度降低。 (除了不同的返回值。)
答案 1 :(得分:1)
D++
和S++
正在递增指针。
请注意,D[i]
相当于*(D + i)
。
因此,一个是递增指针,另一个是保持基数并添加偏移量。
现代编译器可能会编译为相同的代码。
注意:我假设第二个示例中的return D;
是复制粘贴错误,因为它应该是return destination;
,因为D
是递增的并指向“目标字节之后的内存”。
答案 2 :(得分:0)
在编写此代码时,避免向指针添加索引可能会更快。
在我自己的x86体系结构测试中,它在低级指令中内置了索引模式,索引方法稍快一些。
答案 3 :(得分:0)
算法是等效的。第二个版本使用pointer arithmetic将指针前进到下一个位置,而不是使用a[i]
语法索引数组。
这是有效的,因为a[i]
实际上是*(a+i)
的简写(读取:提前i
位置超过a
并读取该位置的值)。它不是在每次迭代时执行总偏移量(+i
),而是在每次迭代时执行部分偏移(++a
)并累积结果。
答案 4 :(得分:0)
虽然两者在语义上都是一样的,但*s++
版本将避免必须将初始指针值作为通过正在复制的数组的字节的增量来偏移。换句话说,s[i]
的“基础”表示实际上是*(s + i*sizeof(type))
,乘法,特别是i
的大值,比小值的简单增量慢得多,至少取决于机器架构。
最后,由于使用了与机器相关的优化内存复制程序集指令,因此libc
memcpy
的实现速度将比您在C中手写的任何内容快得多。不能故意通过普通的C代码访问。
答案 5 :(得分:0)
让您感到困惑的是指针算术:D++
,S++
。指针正在递增以引用下一个char
(因为它们是char*
)
答案 6 :(得分:0)
这是由GCC编译的内部循环。我刚刚添加了restrict
个关键字并删除了返回值,并为Core 2编译了32位:
第一个,数组版本:
.L3:
movzbl (%edi,%edx), %ecx
addl $1, %eax
cmpl %ebx, %eax
movb %cl, (%esi,%edx)
movl %eax, %edx
jne .L3
第二个,增量版本:
.L9:
movzbl (%edx), %ebx
addl $1, %ecx
addl $1, %edx
movb %bl, (%eax)
addl $1, %eax
cmpl %ecx, %esi
ja .L9
正如您所见,编译器可以看到两种结构。
答案 7 :(得分:0)
虽然这两种方式都没有什么区别,但看到其中任何一种,我都会感到有些惊讶。我期待更接近:
void* memcpy (void *destination, const void *source, size_t num) {
char *S = (char *)source;
char *D = (char *)destination;
while (--num)
*D++ = *S++;
return destination;
}
大多数体面的编译器无论如何都会产生相同的代码。我最近没有检查,但有一次大多数针对x86的编译器会将大部分循环转换为单个rep movsd
指令。他们可能不会再这样了 - 这已不再是最佳选择了。