memmove,memcpy 函数的分别实现

来源:互联网 发布:ubuntu怎么运行py文件 编辑:程序博客网 时间:2024/06/05 23:47
memmove,memcpy 函数的分别实现;这两个函数不以'\0'结束符来区分字符,换句话说并不将参数当作字符来看待。memcpy对重叠区的复制会出现问题,但memove不会。

以下来源于 Linux C编程一站式学习

#include <stdio.h>
//#include <string.h>

void *memmove(void *dest, const void *src, size_t n);
void *memcpy(void *dest, const void *src, size_t n);


int main (void)
{
    char buf[20] = "hello world\n";
    printf("memmove(buf + 1, buf, 13) = %s\n", memmove(buf + 1, buf, 13));
    printf("memcpy(buf + 1, buf, 13) = %s\n", memcpy(buf + 1, buf, 13));
    printf(buf);

    return 0;
}

void *memmove(void *dest, const void *src, size_t n)
{
    char temp[n];
    int i;
    char *d = dest;
    const char *s = src;

    for (i = 0; i < n; i++)
        temp[i] = s[i];
    for (i = 0; i < n; i++)
        d[i] = temp[i];

    return dest;
}

void *memcpy(void *dest, const void *src, size_t n)
{
    char *d = dest;
    const char *s = src;
    int *di;
    const int *si;

    int r = n % 4;
    while (r--)
        *d++ = *s++;
    di = (int *)d;
    si = (const int *)s;
    n /= 4;
    while (n--)
        *di++ = *si++;

    return dest;
}

结果:
memmove(buf + 1, buf, 13) = hello world

memcpy(buf + 1, buf, 13) = hhello wwrldd
buf=hhhello wwrldd

在32位的x86平台上,每次拷贝1个字节需要一条指令,每次拷贝4个字节也只需要一条指令,memcpy函数的实现尽可能4个字节4个字节地拷贝,因而得到上述结果

C99的restrict关键字

我们来看一个跟memcpy/memmove类似的问题。下面的函数将两个数组中对应的元素相加,结果保存在第三个数组中。

void vector_add(const double *x, const double *y, double *result){ int i; for (i = 0; i < 64; ++i) result[i] = x[i] + y[i]; }

如果这个函数要在多处理器的计算机上执行,编译器可以做这样的优化:把这一个循环拆成两个循环,一个处理器计算i值从0到31的循环,另一个处理器计算i值从32到63的循环,这样两个处理器可以同时工作,使计算时间缩短一半。但是这样的编译优化能保证得出正确结果吗?假如result和x所指的内存区间是重叠的,result[0]其实是x[1],result[i]其实是x[i+1],这两个处理器就不能各干各的事情了,因为第二个处理器的工作依赖于第一个处理器的最终计算结果,这种情况下编译优化的结果是错的。这样看来编译器是不敢随便做优化了,那么多处理器提供的并行性就无法利用,岂不可惜?为此,C99引入restrict关键字,如果程序员把上面的函数声明为void vector_add(const double *restrict x, const double *restrict y, double *restrict result),就是告诉编译器可以放心地对这个函数做优化,程序员自己会保证这些指针所指的内存区间互不重叠。

由于restrict是C99引入的新关键字,目前Linux的Man Page还没有更新,所以都没有restrict关键字,本书的函数原型都取自Man Page,所以也都没有restrict关键字。但在C99标准中库函数的原型都在必要的地方加了restrict关键字,在C99中memcpy的原型是void *memcpy(void * restrict s1, const void * restrict s2, size_t n);,就是告诉调用者,这个函数的实现可能会做些优化,编译器也可能会做些优化,传进来的指针不允许指向重叠的内存区间,否则结果可能是错的,而memmove的原型是void *memmove(void *s1, const void *s2, size_t n);,没有restrict关键字,说明传给这个函数的指针允许指向重叠的内存区间。在restrict关键字出现之前都是用自然语言描述哪些函数的参数不允许指向重叠的内存区间,例如在C89标准的库函数一章开头提到,本章描述的所有函数,除非特别说明,都不应该接收两个指针参数指向重叠的内存区间,例如调用sprintf时传进来的格式化字符串和结果字符串的首地址相同,诸如此类的调用都是非法的。本书也遵循这一惯例,除非像memmove这样特别说明之外,都表示“不允许”。


memcpy的效率会比memmove高一些,如果还不明白的话可以看一些两者的实现:

  1. void *memmove(void *dest, const void *source, size_t count)
  2. {
  3. assert((NULL != dest) && (NULL != source));
  4. char *tmp_source, *tmp_dest;
  5. tmp_source = (char *)source;
  6. tmp_dest = (char *)dest;
  7. if((dest + count<source) || (source + count) <dest))
  8. {// 如果没有重叠区域
  9.    while(count--)
  10.       *tmp_dest++ = *tmp_source++;
  11. }
  12. else
  13. { //如果有重叠
  14. tmp_source += count - 1;
  15. tmp_dest += count - 1;
  16. while(count--)
  17.     *--tmp_dest = *--tmp;
  18. }
  19. return dest;
  20. }
  21. void *memcpy(void *dest, const void *source, size_t count)
  22. {
  23. assert((NULL != dest) && (NULL != source));
  24. char *tmp_dest = (char *)dest;
  25. char *tmp_source = (char *)source;
  26. while(count --)//不对是否存在重叠区域进行判断
  27.     *tmp_dest ++ = *tmp_source ++;
  28. return dest;
  29. }