模拟实现库函数memcpy--复制内存块。详细理解内存重叠及精准复制问题

简介: 模拟实现库函数memcpy--复制内存块。详细理解内存重叠及精准复制问题

一.对库函数memcpy的了解



通过在MSDN或者cplusplus网站上检索memcpy

aef899bd9c784d0ead7b058d500df120.png


通过对memcpy的检索,可以初步了解到memcpy以下信息:


1.有三个参数,目标指针,源头指针均为泛型指针 void* () (具有通用性,且不能直接被解引用

2.该函数不检查源中是否有任何终止空字符 , 它始终精确地复制数字字节

3.为避免溢出,目标参数和源参数所指向的数组的大小应至少为 num 个字节

4.目标数据和源数据不能重叠


二.模拟实现库函数memcpy



思路分析:


1.通过对库函数memcpy的了解,它的前面两个参数都是void类型的泛型指针,无法直接解引用,并且用户使用时候可能传入的是int 、char、float等类型中的一种,这对传入后复制多少个字节的内容带来了一定难度,因此这个void的参数应该转换为什么类型是我们应该首要考虑的


假设如下图,将参数强转为int类型进行操作,那么arr+1将跳过四个字节,如果传入的size_t num=6,那么将对第二个字节的内容读取前面两个字节的内容,进行解引用以后会产生影响,因此,在这应当选择操作力度最小的char类型进行操作,一次跳过一个字节,就能很好的规避上面的问题

580f0e9951214bc9be21175d3512a3fb.png


2.循环num次,将源头数据的内容覆盖到目标数组中


代码如下:

#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* str1, const void* str2, size_t num)
{
  assert(str1 && str2);
  void* p = str1;//返回目标地址,记录目标地址起始位置
  while (num--)
  {
    *(char*)str1 = *(char*)str2;
    str1 =(char*)str1+1;
    str2 =(char*)str2+1;
  }
  return p;
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[20] = "hh";
  my_memcpy(arr2, arr1, 3);
  printf("%s\n", arr2);
  return 0;
}


运行结果如下:

04998ff41820424ea531acd2f2f334a8.png


三.对库函数memcpy的具体说明



1.如果需要复制int类型的数据该如何


若为其他类型,在传参时,传入的字节数尤为重要,只需要将你想要的复制的元素个数乘上类型的大小即可

#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* str1, const void* str2, size_t num)
{
  assert(str1 && str2);
  void* p = str1;//返回目标地址,记录目标地址起始位置
  while (num--)
  {
    *(char*)str1 = *(char*)str2;
    str1 =(char*)str1+1;
    str2 =(char*)str2+1;
  }
  return p;
}
int main()
{
  int arr1[] = {1,2,3,4};
  int arr2[20] = {0};
  int sz=sizeof(arr1)/sizeof(arr1[0]);
  my_memcpy(arr2, arr1, 3*sizeof(int));
  for(i=0;i<sz;i++)
  {
     printf("%d ",arr2[i]);
  }
  return 0;
}


运行结果如下:

4ef1f1350d7743ab8ff9bd09c7ad774d.png


2.它始终精确地复制数字字节


对于开头提到的该函数不检查源中是否有任何终止空字符 ,它始终精确地复制数字字节。这句话表明无论源头数据是否具有\0,它会连同\0一同复制到目标数据中。


代码如下:

int main()
{
  char arr1[] = "a\0bcdef";
  char arr2[20] = "hh";
  my_memcpy(arr2, arr1, 3);
  printf("%s\n", arr2);
  return 0;
}


运行结果如下:

0a95ed3637ed4d38aa61e9731223ee2d.png


可以看到,原本应当打印a\0b,由于打印的时字符串,因此只打印了a,通过观察内存可以验证该结果


调试观察内存可以看到:

8b265dbeab6648829ce9d408e3758a39.png


因此,库函数memcpy会将num个字节的内容复制到目标空间中,终止符也可能会被复制。


3.覆盖目标空间的内容

通过(2)上面代码的运行结果可以看到,arr2中原本有“hh”这个字符串,但是最后结果内存中却只有a\0b无论arr2这个源头空间中原本是否有数据,memcpy都会将其覆盖。


4.目标数据和源数据不能重叠(重点理解)


当出现下面这种情况时,数据会出现丢失

例如:

int main()
{
  char arr1[] = "abcdef";
  char arr2[20] = "hh";
  my_memcpy(arr1+2, arr1, 4);
  //         str1    str2
  printf("%s\n", arr1);
  return 0;
}

52d3e75fcff64b96a852ac7d1ab54c5c.png



当a覆盖到c的位置时,c已经被覆盖成了a,st2指向原本c的位置时,拷贝给st1的内容就是a了,不是原本的c造成了数据的丢失:

image.png


运行结果如下:


image.png


因此,目标数据与源头数据的内存不能重叠,否则就会发生数据拷贝内容被覆盖,数据出现偏差。


5.防止越界


由于库函数memcpy时将源头的num个字节的内容覆盖到目标内存中,因此,目标内存有可能出现越界情况。

当出现下面情况时,会发生越界崩溃

32e07ad5d2b341bba26351079636ca89.png


int main()
{
  char arr1[] = "abcdef";
  char arr2[20] = "hh";
  my_memcpy(arr1+4, arr1, 4);
  //         str1    str2
  printf("%s\n", arr1);
  return 0;
}


st1向后访问num个字节以后比sizeof(arr1)大的情况下,会发生越界,应当使用时防止该情况发生。

103558414d8042c1bbe47efd232c4a0c.png



运行结果如下:

c7d3febfe0a24ef0876e4c76f1ef9170.png


相关文章
|
22天前
|
程序员 C语言
C语言库函数 — 内存函数(含模拟实现内存函数)
C语言库函数 — 内存函数(含模拟实现内存函数)
30 0
|
2月前
|
编译器 C语言 C++
【C语言】realloc()函数详解(动态内存开辟函数)
【C语言】realloc()函数详解(动态内存开辟函数)
29 0
|
7天前
|
C语言
C语言:内存函数(memcpy memmove memset memcmp使用)
C语言:内存函数(memcpy memmove memset memcmp使用)
|
10天前
|
编译器 C语言
字符串与内存函数
字符串与内存函数
24 0
|
20天前
|
编译器 C++
C++ 解引用与函数基础:内存地址、调用方法及声明
C++ 中的解引用允许通过指针访问变量值。使用 `*` 运算符可解引用指针并修改原始变量。注意确保指针有效且不为空,以防止程序崩溃。函数是封装代码的单元,用于执行特定任务。理解函数的声明、定义、参数和返回值是关键。函数重载允许同一名称但不同参数列表的函数存在。关注公众号 `Let us Coding` 获取更多内容。
136 1
|
21天前
|
Java 程序员 编译器
C语言中灵活多变的动态内存,malloc函数 && free函数&& calloc函数 && realloc函数
C语言中灵活多变的动态内存,malloc函数 && free函数&& calloc函数 && realloc函数
|
25天前
|
程序员 编译器 C语言
【C语言】动态内存管理之4个内存函数`malloc`,`free`,`calloc`和`realloc`深度了解
【C语言】动态内存管理之4个内存函数`malloc`,`free`,`calloc`和`realloc`深度了解
|
2月前
|
编译器 C语言 C++
【C语言】calloc()函数详解(动态内存开辟函数)
【C语言】calloc()函数详解(动态内存开辟函数)
25 0
|
6天前
|
Linux
Linux rsyslog占用内存CPU过高解决办法
该文档描述了`rsyslog`占用内存过高的问题及其解决方案。
29 4
|
29天前
|
移动开发 运维 监控
掌握Linux运维利器:查看CPU和内存占用,轻松解决性能问题!
掌握Linux运维利器:查看CPU和内存占用,轻松解决性能问题!