C++ C++;memcpy函数,为什么使用*s++;而不是s[i]
我从头开始编写memcpy,并一直在查找其他人的实现……我的实现是:C++ C++;memcpy函数,为什么使用*s++;而不是s[i],c++,memcpy,C++,Memcpy,我从头开始编写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*me
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\u t num)
{
char*D=(char*)目的地;
char*S=(char*)源;
for(int i=0;i
我研究过的各种其他来源和参考文献
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;
}
void*memcpy(void*destination,const void*source,size\u t num)
{
char*D=(char*)目的地;
char*S=(char*)源;
for(int i=0;i
我很难理解这种差异以及它们是否会产生不同的输出。让我特别困惑的部分是D++;和S++ 现代编译器将对这些代码进行优化,以获得相同的代码。这被称为强度降低。(除了不同的返回值。)
D++
和S++
正在递增指针
请记住,D[i]
相当于*(D+i)
因此,一个是递增指针,另一个是保持基数并添加偏移量
现代编译器可能会编译成相同的代码
注意:我假设
返回D第二个示例中的code>是复制粘贴错误,因为它应该是返回目的地代码>因为D
是递增的,并指向内存“after”目标字节 编写此代码时,避免向指针添加索引可能会更快
在我自己对x86体系结构的测试中,该体系结构在底层指令中内置了索引模式,索引方法稍微快一些。算法是等效的。第二个版本使用将指针前进到下一个位置,而不是使用a[i]
语法索引数组
这是因为a[i]
实际上是*(a+i)
的简写(读:advancei
定位在a
之前,并读取该位置的值)。它不是在每次迭代时执行总偏移量(+i
),而是在每次迭代时执行部分偏移量(+a
),并累加结果。虽然两者在语义上的意思相同,*s++
版本将避免在复制数组的字节中递增时偏移初始指针值。换句话说,s[i]
的“底层”表示实际上是*(s+i*sizeof(type))
,并且乘法,尤其是i
的大值的乘法,比简单的小值增量要慢得多,至少取决于机器结构
但最终,memcpy
的libc
实现将比用C编写的任何东西都要快得多,因为使用了依赖于机器的优化内存复制汇编指令,您无法通过普通C代码故意访问这些指令。让您困惑的是指针算法:D++
,S++
。指针正在递增,以引用下一个char
(正如它们是char*
)以下是由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
正如您所看到的,编译器正确地看穿了这两种结构。尽管这两种方式都没有什么区别,但看到这两种情况我会有点惊讶。我希望有更接近于:
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
指令。不过,它们可能不再是——这不再是最优的。随机注释:您应该声明i
为size\u t
notint
Random note 2:第二个版本返回不同的指针值。随机注释3:使用自定义memcpy
,您永远不会获得更好的性能。嗯,至少99%的情况下不需要,也许更多。问题的可能重复随机注4:在第二个片段中,你不需要i。只是(当num--)*D++=*S++;返回目的地;不,不是,应该是D+i*sizeof(char)。如果编译器不优化乘法,则乘法将比指针中的加法慢得多increment@Rado由于sizeof(char)
始终为1,因此没有区别。<代码> SigeOS/<代码>操作符返回字符大小。@ RADO实际上是<代码> D+I < /CUT>两个原因:1,C++做指针算术,2,<代码> siZeOf(char)=1</COD>总是,所以即使C++没有做指针算术,也将是<代码> D+I < /代码>。但是他忘记了<代码> */COD> > RADO:C++中的指针算术已经包含了数组中的元素<代码> > t>代码>的<代码> sieOf(t)< /c>乘法。@拉多,它不是<代码> d+i*sieOf(char)< /C>。事实上,我忘记了刚才添加的*
。感谢您指出。事实上,AFAIKx86对索引数组有特殊的说明。如果处理器管道看到这些指令,那么它可能会执行更高效的缓存。再说一遍,这真的取决于编译器生成的机器指令。你真的测试过这个吗?通过简单地使用指针而不是使用索引来访问指针,我已经使一段代码的效率提高了很多。然而,我已经在一个编译器中实现了这个精确的优化。这是众所周知的。如果两个函数都被修改为不返回任何内容,大多数编译器将生成相同的(或实际上相同的)结果