memcpy,memmove函数详解

文章详细介绍了C/C++标准库中的memcpy和memmove函数,强调了它们在内存拷贝中的作用、参数解释、使用示例以及注意事项,包括内存重叠、数据完整性、空指针处理和越界访问的潜在风险。
摘要由CSDN通过智能技术生成

memcpy

memcpy 是一个在 C 和 C++ 中常用的库函数,用于从源内存块复制指定数量的字节到目标内存块。这个函数定义在 <string.h>(C)或 <cstring>(C++)头文件中。

函数原型如下:

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


参数说明:

  • dest:指向目标内存块的指针,即数据将要被复制到的位置。
  • src:指向源内存块的指针,即数据被复制的来源位置。
  • n:要复制的字节数。

返回值:

memcpy 返回 dest 的值(即目标内存块的指针),但通常在实际编程中这个返回值并不被使用,因为复制操作是否成功通常是通过其他方式(如检查是否发生了内存访问错误)来判断的。

使用举例

以下是一个使用 memcpy 函数的简单例子,该例子展示了如何从一个字符数组(源)复制数据到另一个字符数组(目标):

 #include <stdio.h>  
 #include <string.h>  
   int main() {  
 char src[] = "Hello, World!"; // 源字符串  
 char dest[20]; // 目标字符串数组,大小足够大以容纳源字符串和额外的终止符'\0'  
   // 使用memcpy复制src到dest,注意不包括终止符'\0',因为memcpy只复制指定数量的字节  
 // strlen(src) + 1 是为了包括终止符'\0'  
 // 但是因为memcpy不处理字符串的终止符,所以我们需要手动处理  
 memcpy(dest, src, strlen(src) + 1); // 复制字符串和终止符  
   // 或者,如果我们知道要复制的字节数,我们可以直接指定它  
 // 例如,如果我们只想复制前5个字符(不包括终止符),则:  
 // memcpy(dest, src, 5);  
 // dest[5] = '\0'; // 手动添加终止符  
   // 打印目标字符串  
 printf("Source: %s\n", src);  
 printf("Destination: %s\n", dest);  
   return 0;  
 } 

在这个例子中,我们使用了 strlen(src) + 1 来计算要复制的字节数,包括字符串的终止符 '\0'。这是因为在 C 语言中,字符串是以空字符 '\0' 结尾的字符数组,而 memcpy 只是一个简单的内存拷贝函数,它不知道什么是字符串的终止符,所以我们必须确保在复制后手动添加它(如果需要的话)。
另外,请注意在实际使用中,目标数组 dest 必须足够大以容纳源数据加上任何额外的字节(如字符串的终止符)。如果目标数组太小,memcpy 会导致缓冲区溢出,这是一种严重的安全漏洞。在上面的例子中,我们假设 dest 数组的大小是20,这足够大以容纳 "Hello, World!" 字符串及其终止符。

注意事项:

内存重叠:如果源内存块和目标内存块重叠,memcpy 可能会导致不可预期的结果。在这种情况下,应该使用 memmove 函数,它专门处理内存重叠的情况。

 #include <stdio.h>  
 #include <string.h>  
   int main() {  
 char arr[10] = "abcdefg";  
 printf("Before: %s\n", arr);  
   // 尝试使用 memcpy 复制字符串到自身内部,这将导致重叠  
 memcpy(arr + 2, arr, 5); // 这将覆盖 arr[2] 到 arr[6] 的内容  
   // 注意:这可能导致不可预期的结果,因为 memcpy 不处理重叠  
 printf("After (with memcpy): %s\n", arr); // 输出可能是不确定的  
   // 如果需要处理重叠,应该使用 memmove  
 // 假设我们想要恢复原始数组并再次尝试使用 memmove  
 strcpy(arr, "abcdefg");  
 printf("Restored: %s\n", arr);  
   memmove(arr + 2, arr, 5); // 这将正确处理重叠  
 printf("After (with memmove): %s\n", arr); // 输出是确定的  
   return 0;  
 } 

数据完整性:memcpy 只是简单地复制字节,不会考虑数据类型或任何特定的内存对齐。如果源或目标内存块包含具有特定结构的数据(如结构体或数组),并且这些数据的表示或对齐在源和目标之间有所不同,那么使用 memcpy 可能会导致数据损坏或未定义的行为。

 #include <stdio.h>  
 #include <string.h>  
   typedef struct {  
 int id;  
 char name[20];  
 } Person;  
   int main() {  
 Person person1 = {1, "Alice"};  
 Person person2;  
   // 假设我们只想复制 name 字段,但错误地复制了整个结构体  
 memcpy(&person2, &person1, sizeof(person1.name)); // 这只复制了 name 的大小,但使用了整个结构体的指针  
   // 注意:person2.id 现在可能是一个随机值,因为它没有被正确初始化  
 printf("Person2 ID: %d, Name: %s\n", person2.id, person2.name); // ID 可能是不确定的  
   // 正确的做法是只复制需要的字段  
 memcpy(person2.name, person1.name, sizeof(person1.name));  
 person2.id = 0; // 或者设置为其他合适的值  
   printf("Corrected Person2 ID: %d, Name: %s\n", person2.id, person2.name); // ID 和 Name 都是确定的  
   return 0;  
 } 

空指针和无效指针:memcpy 不会检查源或目标指针是否为空或无效。如果传递了空指针或无效指针给 memcpy,可能会导致程序崩溃或其他未定义的行为。

 #include <stdio.h>  
 #include <string.h>  
   int main() {  
 char *src = NULL; // 空指针  
 char dest[10];  
   // 尝试使用空指针进行 memcpy 将导致未定义的行为  
 // memcpy(dest, src, 5); // 这将引发错误,因为 src 是 NULL  
   // 应该先检查指针是否为空  
 if (src != NULL) {  
 memcpy(dest, src, 5);  
 } else {  
 printf("Source pointer is NULL!\n");  
 }  
   // 对于无效指针(即指向不可访问内存的指针),情况类似  
 // 但通常这些指针是在运行时动态分配或获取的,因此检查更为复杂  
   return 0;  
 } 

越界访问:如果复制操作导致访问了超出目标或源内存块边界的内存,可能会导致程序崩溃、数据损坏或未定义的行为。因此,在使用 memcpy 时,必须确保 n 的值不会超过源或目标内存块的实际大小。

#include <stdio.h>  
#include <string.h>  
  
int main() {  
    char src[] = "Hello, World!";  
    char dest[5]; // 目标数组太小,无法容纳整个源字符串  
      
    // 尝试复制超过目标数组大小的字节将导致越界访问  
    memcpy(dest, src, strlen(src) + 1); // 这将复制包括空字符在内的整个字符串  
      
    // 这将导致未定义的行为,因为 dest 没有足够的空间来存储整个字符串  
    // 在实际中,可能会覆盖 dest 数组之后的内存区域,导致数据损坏或其他问题  
      
    // 应该确保不会复制超过目标数组大小的字节  
    memcpy(dest, src, sizeof(dest) - 1); // 只复制 dest 能容纳的字节数,并确保留有空间给空字符  
    dest[sizeof(dest) - 1] = '\0'; // 手动添加空字符以确保字符串正确终止  
      
    printf("Dest: %s\n", dest); // 输出将被截断,但程序不会崩溃或产生未定义行为  
      
    return 0;  
}

 memmove

memmove 是 C 语言标准库中的一个函数,用于在内存中移动一定数量的字节。与 memcpy 类似,memmove 也用于从源内存块复制指定数量的字节到目标内存块。但是,memmove 的主要优势在于它能够正确处理源内存块和目标内存块重叠的情况。

函数原型如下:

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

参数说明:

  • dest:指向目标内存块的指针,即数据将要被复制到的位置。
  • src:指向源内存块的指针,即数据被复制的来源位置。
  • n:要复制的字节数。

返回值:

  • memmove 返回 dest 的值(即目标内存块的指针),但通常在实际编程中这个返回值并不被使用,因为复制操作是否成功通常是通过其他方式(如检查是否发生了内存访问错误)来判断的。

使用 memmove 的一个典型场景是当源内存块和目标内存块重叠时。在这种情况下,使用 memcpy 可能会导致未定义的行为,因为 memcpy 假设源内存块和目标内存块不会重叠。而 memmove 则通过一种类似于使用中间缓冲区的方式来进行复制,从而允许源内存块和目标内存块重叠。

以下是一个使用 memmove 的简单例子:

 #include <stdio.h>  
 #include <string.h>  
   int main() {  
 char str[] = "Hello, World!";  
 printf("Original string: %s\n", str);  
   // 假设我们想要将字符串中的 "World" 向前移动 5 个字符的位置  
 // 注意这里源和目标内存块是重叠的  
 memmove(str + 5, str + 7, 5); // 将 "World" 向前移动 5 个字符的位置  
   // 注意:由于 memmove 允许重叠,所以这里可以正常工作  
 // 而如果使用 memcpy,则可能会导致未定义的行为  
   printf("Modified string: %s\n", str); // 输出 "Hello, Worldl!"  
   return 0;  
 } 

在这个例子中,我们使用 memmove 将字符串中的 "World" 向前移动了 5 个字符的位置。由于源内存块(str + 7)和目标内存块(str + 5)重叠,因此使用 memmove 是正确的选择。如果使用 memcpy,则可能会导致未定义的行为。


注意事项:

  • 当你知道源内存块和目标内存块可能重叠时,应该使用 memmove 而不是 memcpy。
  • memmove 不保证比 memcpy 更慢,但在某些情况下(如内存重叠时),它可能不得不使用更复杂的算法来保证正确的行为。
  • memmove 和 memcpy 一样,不会检查源或目标指针是否为空或无效。如果传递了空指针或无效指针给 memmove,可能会导致程序崩溃或其他未定义的行为。
  • 同样的,越界访问也是使用 memmove 时需要避免的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值