C库函数详解 - 内存操作函数:memcpy()、memmove()、memset()、memcmp() (一)

简介: `memcpy()` 和 `memmove()` 是C语言中的两个内存操作函数。`memcpy()` 函数用于从源内存区域复制指定数量的字节到目标内存区域。它不处理内存重叠的情况,如果源和目标区域有重叠,结果是未定义的。函数原型如下:```cvoid *memcpy(void *dest, const void *src, size_t num);```

一、memcpy()

函数原型


void * memcpy ( void * dest, const void * src, size_t num );


参数说明


函数 memcpy 从 src 位置开始向后复制 num 个字节的数据到 dest 的内存位置。

void * dest 代表目标的内存地址,const void * src 代表源内存地址。其中二者的数据类型均为 void * 。void * 可以存储任何类型地址的值。因此,该函数拷贝的内存数据可以是任意类型(如果int、float、double等均可)。

size_t 即unsigned int类型。注意:num代表的是要拷贝的字节数,而非元素个数!如要从src拷贝一个int类型的数据到dest,则num应传入4,而非1.

该函数的返回值类型也为 void * ,也即只返回目标地址的数值。后续如何按照基类型取出数据、使用数据,可以由调用方在调用函数后实现。


模拟算法


void* my_memcpy(void* dest, const void* src, size_t num) {
    void *ret = dest;    //保存dest,用于最终输出
 
    assert(dest);
    assert(src);
 
  while (num--) {
    *(char*)dest = *(char*)src1;    //(char*): 取出每一个字节(8 bit)的值,以单个字节为单位进行赋值
    dest = (char*)dest + 1;    
    src = (char*)src + 1;
  }    //void * 类型是不能直接运算的,因为没有步长。(char*)将dest与src转换为以char为步长,再向后移动
 
  return ret;
}


  • 该函数用于实现没有重叠部分内存的内存数据拷贝。如果sourcedestination有任何的重叠,复制的结果都是未定义的。


  • 拷贝内存值按从低地址到高地址的顺序进行,多用于数组。



  • 根据memcpy()的模拟算法,如果src与dest的内存空间有重叠部分,则可能导致src中的内容被覆盖,无法输出正确的值。



src中元素3的位置恰好也是dest中首元素的位置。dest的首元素被更改的同时,src中的元素也被更改。因此,memcpy()是不可用于“自己拷贝到自己后面”这样的操作的。这一问题留给了memmove()来解决。


使用示例


1.简单


int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[10] = { 0 };
  memcpy(arr2, arr1, 20);    //拷贝20个字节,即5个int元素
 
  float arr3[] = { 1.0f,2.0f,3.0f,4.0f };
  float arr4[5] = { 0.0 };
  memcpy(arr3, arr4, 8);    //拷贝8个字节,即2个float元素
 
  return 0;
}


2.进阶


//示例来自cplusplus官网
 
/* memcpy example */
#include <stdio.h>
#include <string.h>
 
struct {
  char name[40];
  int age;
} person, person_copy;
 
int main ()
{
  char myname[] = "Pierre de Fermat";    //定义一个字符串
 
  /* 用 memcpy 拷贝字符串 */
  //每个char类型占一个字节,因此要拷贝的字节数即strlen()+1,加一是因为要把'\0'也拷贝过去。
  memcpy ( person.name, myname, strlen(myname)+1 );
  person.age = 46;
 
  /* 用 memcpy 拷贝结构体 */
  //sizeof操作符,可以直接得到结构体变量在内存中所占的字节数。
  memcpy ( &person_copy, &person, sizeof(person) );
  
  //直接完成了结构体之间的数据拷贝:从person拷贝到person_cpy,不用手动转义,非常方便
  printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
 
  return 0;
}


二、memmove()


函数原型


void * memmove ( void * dest, const void * src, size_t num );


参数说明


  • 该函数的参数与返回值类型与memcpy()函数相同。该函数同样用作 从 src 位置开始向后复制 num 个字节数据到 dest 的内存位置。


  • memmove()函数与memcpy()函数主要的区别在于memmove()可以进行有内存重叠的数据拷贝,而memcpy()绝对不能。memmove()的功能比memcpy()更加完善。


  • 可以理解为:如果memcpy()函数够到了60分,那么memmove()函数却能到达90分。


模拟算法


#include<stdio.h>
#include<string.h>
 
//模拟实现memmove()
void* my_memmove(void* dest, const void* src, size_t num) 
{
  void* ret = dest; //保存结果用于输出
 
  //从前向后拷贝,也可以写成if(dest <= src || (char*)dest >= (char*)src + num)
  if (dest <= src)
  {
    while (num--) {
      *(char*)dest = *(char*)src;    //拷贝
      dest = (char*)dest + 1;   
      src = (char*)src + 1;     //指针从前向后移动(从低地址向高地址移动)
    }
 
  }
  else    //从前向后拷贝
  {
    dest = (char*)dest + num - 1;    
    src = (char*)src + num - 1;    //初始化两指针至各自范围的最后
 
    while (num--) {
      *(char*)dest = *(char*)src;    //拷贝    
      dest = (char*)dest - 1;    
      src = (char*)src - 1;    //指针从后向前移动
    }
  }
  return ret;    //返回值为dest
}
//测试代码///
int main() {
  int arr1[] = { 2,3,4,5,6 };
  my_memmove(arr1+2, arr1, 8);
  //预计 2 3 2 3 6
  for (int i = 0; i < 5; i++) {
    printf("%d ", arr1[i]);
  }
  printf("\n-------------------\n");
  
  int arr2[] = { 2,3,4,5,6 };
  memmove(arr2 + 2, arr2, 8);
 
  for (int i = 0; i < 5; i++) {
    printf("%d ", arr2[i]);
  }
 
  return 0;
}


拷贝顺序的结论如图所示。上面提到,当有内存重叠时,拷贝的顺序是有讲究的。若不遵守下图的结论, 仍将导致src中原来的值被覆盖,无法输出正确的结果。



***错误的模拟算法


如下代码是错误的:


//错误的代码
void* my_memmove(void* dest, const void* src, size_t num)
{
    void * ret = dest;
    while (num--) {
  //从前向后拷贝
      if (dest <= src)
      {
        *(char*)dest = *(char*)src;
        dest = (char*)dest + 1;
        src = (char*)src + 1;
      }
      else
      {
        dest = (char*)dest + num - 1;    //错误
        src = (char*)src + num - 1;    //错误
        *(char*)dest = *(char*)src;
        dest = (char*)dest - 1;
        src = (char*)src - 1;
    
      }
    return ret;
}


有一些同学可能认为,while(num--)语句与if语句的顺序可以调换。正确的代码是先进行情况判断,再进入while(num--)进行赋值与移动。将二者顺序调换,乍一看没有什么问题,是先设定一共要移动num次,再进入判断进行具体的操作。但是这样书写是有问题的,因为在dest > src && dest < src+num时,需要从后向前拷贝,这意味着dest和src的起始位置要发生变化。


上述代码中标记“错误”的语句便是dest和src初始化的语句。如果直接将while和if的位置调换,则每一次进入循环,都要初始化一遍dest和src。如此,dest与src的功能就被打乱了。


使用示例


//示例来自cplusplus官网
 
/* memmove example */
#include <stdio.h>
#include <string.h>
int main ()
{
  char str[] = "memmove can be very useful......";
  memmove (str+20,str+15,11);    //表示将包括str+15向后11个字节的内容移动到str+20位置
  puts (str);
  return 0; 
}


输出:



如下图,将 src = str+11 位置开始,包括该位置共向后拷贝11字节。每个char占一个字节,因此拷贝了"very useful"这7个char字母至dest = str+20的位置。


相关文章
|
2月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
3月前
|
安全 C++
超级好用的C++实用库之环形内存池
超级好用的C++实用库之环形内存池
66 5
|
2月前
|
存储 C语言 C++
来不及哀悼了,接下来上场的是C语言内存函数memcpy,memmove,memset,memcmp
本文详细介绍了C语言中的四个内存操作函数:memcpy用于无重叠复制,memmove处理重叠内存,memset用于填充特定值,memcmp用于内存区域比较。通过实例展示了它们的用法和注意事项。
80 0
|
3月前
|
C++
超级好用的C++实用库之动态内存池
超级好用的C++实用库之动态内存池
41 0
|
4月前
【C初阶】内存函数:memcpy+memmove+memset+memcmp
【C初阶】内存函数:memcpy+memmove+memset+memcmp
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
266 1
|
20天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
29天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
1月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
21 3
|
1月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
48 1