strcpy,strncpy函数详解

ops/2024/9/23 1:41:54/

 strcpy函数

在C语言中,strcpy()函数用于将一个字符串复制到另一个字符串中。

函数原型如下:

char *strcpy(char *destination, const char *source);

参数解释:

  • destination:目标字符串,将会被复制到。
  • source:源字符串,将会被复制到目标字符串中。

函数返回值:

  • 返回指向目标字符串的指针。

场景一:

#include <stdio.h>
#include <string.h>int main()
{char arr[10] = "########";printf("%s\n", strcpy(arr,"hello"));return 0;
}

输出的结果

hello

场景二: 

#include <stdio.h>
#include <string.h>int main()
{char arr1[10] = "**********";char arr2[10] = "abcd";printf("%s\n", strcpy(arr1, arr2));return 0;
}

输出结果

abcd

注意点

1.源字符必须以 '\0'结束:

#include <stdio.h>
#include <string.h>int main()
{char arr1[10] = "**********";char arr2[10] = { 'a','b','c','d' };printf("%s\n", strcpy(arr1,arr2));return 0;
}


这里的代码会出错,'\0'是停止拷贝的终止条件,arr2字符数组所在的内存空间后面存储的内容并不知道,不遇到 '\0' 拷贝就不会停止,这就会导致越界访问,程序就会出现问题。

2.目标空间必须足够大,以确保能存放得下放源字符串

#include <stdio.h>
#include <string.h>int main()
{char arr1[5] = "*****";char arr2[] = "hello world";printf("%s\n", strcpy(arr1,arr2));return 0;
}


 这里虽然拷贝成功并将结果输出了,但程序却崩溃了。目标空间太小,不足以放置拷贝的源字符串,会造成溢出的情况

3.目标空间必须可变

#include <stdio.h>
#include <string.h>int main()
{char* str1 = "hello world";char str2[10] = "*********";printf("%s\n", strcpy(str1,str2));return 0;
}


这里的程序也出现了错误。str1指向的是常量字符串,是不可以被修改掉的,目标空间必须是可以被修改的,因为要将拷贝的字符串放在目标空间中。而源字符串可以是能够修改的、也可以是不能修改的,因为strcpy函数的第二个参数已经用const关键字修饰了,保证了拷贝过程中不会被修改。

模拟实现

strcpy函数的模拟实现方式可以有多种,这主要取决于你对函数安全性、性能以及内存操作的不同需求。以下是几种常见的实现方式:

1. 基本实现

这是最直接的实现方式,它逐个复制源字符串的字符到目标字符串,直到遇到源字符串的结束符\0。

 char *my_strcpy(char *dest, const char *src) {assert(dest&&str);char *orig_dest = dest; // 保存dest的原始指针  while (*src != '\0') {  *dest++ = *src++;  }  // 添加结束符'\0'  *dest = '\0';  // 返回原始dest指针  return orig_dest;  } 

2. 指针算术实现

这种实现方式使用指针算术来简化复制过程,减少了对dest的多次解引用。

 char *my_strcpy_pointer_arithmetic(char *dest, const char *src) {assert(dest&&str);  char *orig_dest = dest;  while ((*dest++ = *src++) != '\0');  return orig_dest;  } 

3. 考虑安全性的实现

为了避免缓冲区溢出,你可以在实现中检查目标字符串dest的缓冲区大小。然而,strcpy函数本身并不检查缓冲区大小,这是其不安全的地方。一个更安全的版本是strncpy,但如果你想要模拟实现一个带缓冲区大小检查的strcpy,可以这样做:

 char *my_safe_strcpy(char *dest, const char *src, size_t dest_size) {assert(dest&&str);char *orig_dest = dest;  size_t i;  // 确保目标缓冲区至少有1个字节的空间以存储'\0'  if (dest_size == 0) {  return NULL;  }  for (i = 0; i < dest_size - 1 && src[i] != '\0'; ++i) {  dest[i] = src[i];  }  // 添加结束符'\0'并确保不会超出目标缓冲区  dest[i] = '\0';  return orig_dest;  } 

在这个实现中,我们传递了一个额外的参数dest_size来表示目标字符串dest的缓冲区大小。我们确保在复制过程中不会超出这个大小,并在最后添加一个结束符\0。

注意事项:

  1. 基本实现和指针算术实现都没有检查目标缓冲区的大小,因此它们可能导致缓冲区溢出。在实际应用中,应该避免使用这些不安全的实现,除非你能确保目标缓冲区足够大。
  2. 考虑安全性的实现通过检查目标缓冲区的大小来防止缓冲区溢出,但这也增加了函数的复杂性。在性能要求较高的场景中,可能需要权衡安全性和性能。
  3. 在实际编程中,通常建议使用标准库提供的strcpy、strncpy等函数,并根据需要选择适当的安全措施,如确保目标缓冲区足够大或使用更安全的字符串处理函数。

strncpy函数

strncpy 是 C 语言标准库中的一个函数,用于将源字符串的前 n 个字符(包括空字符 \0,如果源字符串在 n 个字符之前结束)复制到目标字符串中。

与 strcpy 不同,strncpy 不会自动在目标字符串的末尾添加空字符 \0,除非源字符串的长度小于或等于 n

因此,在使用 strncpy 时,通常需要手动添加空字符以确保目标字符串是一个有效的 C 字符串。

它的原型如下:

char *strncpy(char *dest, const char *src, size_t n);

其中,dest是目标字符串的指针,src是源字符串的指针,n是要复制的字符数。

strncpy函数会将src指向的字符串的前n个字符复制到dest指向的字符串中,并在复制完成后返回dest的指针。

注意点

strncpy不会自动向目标空间追加‘\0’

如果复制的字符数n大于src指向的字符串的长度,那么dest指向的字符串会被'\0'字符填充,直到复制完n个字符为止。

如果n小于src指向的字符串的长度,那么dest指向的字符串不会被终止符'\0'填充,而且可能不是一个有效的C字符串。

示例代码如下:

#include <stdio.h>
#include <string.h>int main() {char src[] = "Hello, World!";char dest[20];strncpy(dest, src, 5);dest[5] = '\0';printf("Copied string: %s\n", dest);return 0;
}

这段代码将src字符串的前5个字符复制到dest字符串中,并手动添加了终止符'\0'。最后输出的结果为"Hello"。

目标空间必须足够大

如果n小于src指向的字符串的长度,那么dest指向的字符串不会被终止符'\0'填充,而且可能不是一个有效的C字符串。

我们上面举了例子,就不多说了

模拟实现

strncpy函数的模拟实现方式可以有多种,但基本的思路都是按照strncpy的定义来进行:复制源字符串的前n个字符到目标字符串,并确保目标字符串至少包含n个字符(如果需要的话,用空字符\0填充剩余部分)。以下是几种可能的模拟实现方式:

1. 基本实现

这是最直接的模拟实现方式,按照strncpy的定义逐字符复制,并在需要时添加空字符\0。

 #include <stddef.h> // 包含 size_t 的定义  char *my_strncpy_basic(char *dest, const char *src, size_t n) {assert(dest&&str);  char *orig_dest = dest;  size_t i;  for (i = 0; i < n && src[i] != '\0'; ++i) {  dest[i] = src[i];  }  // 如果还没有达到 n 个字符,用 '\0' 填充剩余部分  for (; i < n; ++i) {  dest[i] = '\0';  }  return orig_dest;  } 

2. 优化版实现

在某些情况下,如果知道源字符串的长度不会超过n,可以稍微优化这个过程,避免不必要的空字符填充。

 char *my_strncpy_optimized(char *dest, const char *src, size_t n) { assert(dest&&str);char *orig_dest = dest;  size_t i;  for (i = 0; i < n && src[i] != '\0'; ++i) {  dest[i] = src[i];  }  // 如果源字符串自身长度小于 n,确保目标字符串以 '\0' 结尾  if (src[i] == '\0') {  dest[i] = '\0';  }  return orig_dest;  } 

3. 指针算术实现

使用指针算术可以简化循环内部的索引操作,使得代码更简洁。

 char *my_strncpy_pointer_arithmetic(char *dest, const char *src, size_t n) {assert(dest&&str);char *orig_dest = dest;  const char *src_end = src + n; // 计算 src 中要复制的字符的结束位置  // 复制字符直到达到 n 个字符或遇到 src 的结束符 '\0'  while (src < src_end && *src != '\0') {  *dest++ = *src++;  }  // 用 '\0' 填充剩余部分  while (dest < orig_dest + n) {  *dest++ = '\0';  }  return orig_dest;  } 

注意事项:

  1. 无论采用哪种实现方式,都需要确保目标缓冲区dest有足够的空间来存储至少n个字符,包括结尾的空字符\0。如果dest的空间不足,可能会导致缓冲区溢出。
  2. 在实际应用中,还需要考虑错误处理,比如当dest或src为NULL时应该怎么做。
  3. 在性能敏感的场合,可以根据实际情况选择最合适的实现方式。例如,如果源字符串通常很长,使用指针算术可能更有效率;如果源字符串通常较短,基本的循环实现可能就足够了。
  4. 这些模拟实现都是基于标准strncpy函数的行为,并假设调用者已经正确地处理了缓冲区大小和空指针等问题。在真实环境中使用时,还需要添加适当的错误检查和边界条件处理来确保程序的健壮性。

与strcpy的区别

strncpystrcpy都是C语言中的字符串处理函数,它们的主要功能都是复制字符串,但它们在处理字符串的方式上有一些重要的区别。

  1. 复制长度

    • strcpy函数会复制源字符串的所有字符,直到遇到空字符\0为止。这意味着如果源字符串的长度超过了目标字符串的缓冲区大小,那么strcpy可能会导致缓冲区溢出,这是一个常见的安全问题。
    • strncpy函数则复制源字符串的前n个字符到目标字符串中,无论是否遇到空字符\0。如果源字符串的长度小于n,那么strncpy会在目标字符串的剩余位置填充\0,直到复制了n个字符。如果源字符串的长度大于或等于n,strncpy不会添加额外的\0,这可能会导致目标字符串不是一个有效的C字符串(即不以\0结尾)。因此,在使用strncpy时,通常需要手动检查并添加\0,以确保字符串的正确性。
  2. 安全性

    • 由于strcpy不会检查目标缓冲区的大小,因此它更容易导致缓冲区溢出。这种溢出可以被恶意利用,造成严重的安全问题。
    • strncpy虽然在一定程度上可以避免缓冲区溢出(如果正确使用,并且n不超过目标缓冲区的大小),但它并不总是生成有效的C字符串。因此,在使用strncpy时,需要特别注意确保目标字符串的正确性。

总的来说,虽然strcpystrncpy都用于复制字符串,但它们在处理字符串的方式和安全性上有显著的不同。在编写涉及字符串操作的代码时,应根据具体情况选择适当的函数,并确保正确使用它们,以避免潜在的安全问题。


http://www.ppmy.cn/ops/30092.html

相关文章

数学建模--图论最短路径基础

1.图的定义 学过数据结构或者离散数学的小伙伴们应该知道图的概念&#xff0c;我在这里简单的介绍一下&#xff1a; 图的概念和我们理解的是很不一样的&#xff0c;这里的图并不是我们的生活里面的图片&#xff0c;而是一种表示不同的数据之间的关系&#xff0c;例如这里的5个…

Django框架之视图层

一、三板斧的原理介绍 1、HttpResponse 在Django中&#xff0c;HttpResponse是一个类&#xff0c;用于构建HTTP响应并返回给客户端。当视图函数处理完请求后&#xff0c;需要返回一个响应时&#xff0c;就会使用HttpResponse对象。 &#xff08;1&#xff09;创建HttpRespon…

【EI会议|稳定检索】2024年传感技术与图像处理国际会议(ICSTIP 2024)

2024 International Conference on Sensing Technology and Image Processing 一、大会信息 会议名称&#xff1a;2024年传感技术与图像处理国际会议会议简称&#xff1a;ICSTIP 2024收录检索&#xff1a;提交Ei Compendex,CPCI,CNKI,Google Scholar等会议官网&#xff1a;htt…

Vue阶段练习:组件拆分

页面开发思路 分析页面&#xff0c;按模块拆分组件&#xff0c;搭架子&#xff08;局部或全局注册&#xff09;根据设计图&#xff0c;编写html结构css样式拆分封装通用小组件&#xff08;局部或全局注册&#xff09;将来通过js动态渲染实现功能 BaseBrandItem.vue <templ…

ue引擎游戏开发笔记(26)——处理角色死亡敌人仍攻击bug

1.需求分析 对游戏中存在的各种小问题做细节处理&#xff0c;例如玩家在死亡后&#xff0c;敌人仍对着目标开炮&#xff0c;并且仍然触发爆炸效果。 2.操作实现 1.首先分析问题起因&#xff0c;是由于虽然玩家控制的小车被摧毁了&#xff0c;但控制器仍然存在&#xff0c;没有…

设计模式:策略模式

一&#xff0c;策略模式 策略模式&#xff08;Strategy Pattern&#xff09;是一种常用的软件设计模式&#xff0c;属于行为型模式。它的目的是定义一系列算法&#xff0c;并将每个算法封装起来让它们可以互换使用&#xff0c;算法的变化不会影响使用算法的用户。策略模式常用…

设计模式之模板模式

模板模式&#xff08;Template Method Pattern&#xff09;是行为设计模式之一&#xff0c;它定义了一个操作中的算法骨架&#xff0c;而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤&#xff0c;从而达到复用算法框架…

SpringBoot实现图片上传(个人头像的修改)

SpringBootlayui实现个人信息头像的更改 该文章适合对SpringBoot&#xff0c;Thymeleaf&#xff0c;layui入门的小伙伴 废话不多说&#xff0c;直接上干货 Springbootlayui实现头像更换 前端公共部分代码 HTML页面代码 <div class"layui-card-header" style&quo…