前言

C风格的字符串处理函数有很多,如strcpy()、strcat()等等。

strcpy与strcat

char* strcpy (char* dest, const char* src);
char* strcat (char* dest, const char* src);

strcpy将'src'中的字符串按字符拷贝到'dest'中,遇到'0x00'时不拷贝此字符并结束函数,返回"dest"地址。

当"sizeof(dest) > sizeof(src)"时,'src'能成功复制到'dest'中;反之会出现缓冲区溢出的问题,如下代码:

 char dest[5];memset(dest, 0x0, sizeof(dest));char src[10] = "012345678";strcpy(dest, src); //拷贝大于dest数组大小的长度printf("%s\n", dest);

输出结果:“012345678”。我们可以用 "dest[5] = '\0';" 来截断数组,输出正确结果,但是接下来程序会发生未定义行为——

  1. 如果上述代码是被调用函数,且恰巧当前函数栈中位于‘dest’数组最后元素之后的四字节地址记录了上一个函数栈的栈底指针,那么这部分地址信息会被‘src’后面的数据覆盖写,最后弹出栈的不是正确的记录栈底指针的信息,返回的函数栈的栈底位置会是'0x38373635(主机是小端字节序)'。如果此时用户栈访问的是内核信息所在内存地址,程序将会崩溃。

  2. 另外如果被调用函数的返回地址被修改成无权访问的地址,CPU访问这个地址取指令时程序也会崩溃。这就是栈溢出的严重后果。

  3. 如果没有覆盖栈底指针和返回地址,CPU正常把控制权返回给调用者继续执行。

  由此当我们使用strcpy()函数时,必须明确’src‘的大小可控,不可来自用户输入。或者使用动态内存给’dest‘分配大小,来防止缓冲区溢出。

strcat将’src‘按字符追加写到’dest’后面,遇到'0x00'时不拷贝此字符并结束函数,返回dest地址。它同样存在缓冲区溢出问题。

strncpy与strncat

为了防止缓冲区溢出,就需要程序员能够控制拷贝的字节数,可以使用strncpy()和strncat():

char* strncpy (char* dest, const char* src, size_t copySize);
char* strncat (char* dest, const char* src, size_t copySize);

strncpy将从‘src’按字符拷贝‘copySize’个字符到‘dest’,如果中途遇到‘0x00’不拷贝此字符并提前结束函数,返回‘dest’地址。

strncpy源代码:

char * strncpy(char *dest, const char *src, size_t copySize)
{size_t size = __strnlen(src, copySize);if (size != copySize)memset(dest + size, '\0', copySize - size);return memcpy(dest, src, size);
}

其中__strnlen()也是'GNU libc'里的库函数——返回'strlen(src)'和'copySize'的最小值(百度’GNU libc‘可以自行下载它的库函数实现源代码)。不同编译器的实现略有不同,但是功能是相同的。strncpy的实现不考虑‘dest’缓冲区的长度,所以在函数体中它只是比较‘src’长度和‘copySize’大小,取二者最小值的长度拷贝至‘dest’中。它的特点是如果拷贝至‘dest’的数据没有‘0x00’字符,则不会在‘dest’末尾添加结束符,需要程序员手动分配。另外程序员需要根据拷贝至‘dest’字符串的大小和‘src’长度进行比较,来判断是否发生字符串截断。以上是使用此函数时需要程序员做的工作。它的优点是拷贝字符数可供程序员选择,缺点是可能造成缓冲区溢出和需要手动处理截断的问题。为防止缓冲区溢出,一般做法是将‘copySize’置为‘dest’的长度。

strncat源代码:

char * strncat(char *dest, const char *src, size_t copySize)
{char *s = dest;/* Find the end of dest.  */dest += strlen (dest);size_t ss = __strnlen (src, copySize);dest[ss] = '\0';memcpy (dest, src, ss);return s;
}

strncat会覆写’dest‘的首个’0x00‘字符,将’src‘按字符拷贝追加写到’dest‘后面,如果中途遇到’0x00‘或者’copySize‘个字符拷贝完毕,在’dest‘末尾添加结束符最后函数结束,返回’dest‘地址。为防止缓冲区溢出,一般做法是将’copySize‘置为’dest‘剩余空间大小。

strlcpy和strlcat

为了减轻程序员添加结束符和处理字符串截断的负担,可以使用strlcpy和strlcat函数。

strlcpy源代码:

size_t strlcpy(char *dst, const char *src, size_t dsize)
{const char *osrc = src;size_t nleft = dsize;/* Copy as many bytes as will fit. */if (nleft != 0) {while (--nleft != 0) {if ((*dst++ = *src++) == '\0')break;}}/* Not enough room in dst, add NUL and traverse rest of src. */if (nleft == 0) {if (dsize != 0)*dst = '\0';        /* NUL-terminate dst */while (*src++);}return(src - osrc - 1);    /* count does not include NUL */
}

strlcpy将‘src’按字符拷贝到‘dst’中,最多拷贝(dszie-1)个字符,拷贝结束后在‘dst’末尾添加'0x00'结束符,返回值是‘src’的长度。一般将‘dsize’置为‘dst’的大小。相较于strncpy,strlcpy有两个优点:(1)当strlen(src)大于等于‘dsize’时自动在‘dst’末尾添加结束符;(2)返回值大于等于‘dsize’时确定发生字符串截断。以上两点帮助程序员进行判断,方便后续处理。

strlcat源代码:

size_t strlcat(char *dst, const char *src, size_t dsize)
{const char *odst = dst;const char *osrc = src;size_t n = dsize;size_t dlen;/* Find the end of dst and adjust bytes left but don't go past end. */while (n-- != 0 && *dst != '\0')dst++;dlen = dst - odst;n = dsize - dlen;if (n-- == 0)return(dlen + strlen(src));while (*src != '\0') {if (n != 0) {*dst++ = *src;n--;}src++;}*dst = '\0';return(dlen + (src - osrc));    /* count does not include NUL */
}

其中,‘dlen‘是’dst‘中原有字符串的长度(不包含’0x00‘结束符);’dsize‘是’dst‘缓冲区的总大小(包含结束符)。 它首先找到’dst‘中源字符串的结束符,然后计算剩余空间大小,如果为0,则返回(这里不懂’n-- == 0‘的判断,当’dst‘的长度和参数'dsize'的大小一样时’n‘才等于0,不过这种情况只有在程序员没有分配好‘dst’缓冲区大小和‘dst’内原字符串的大小才会发生的吧?如果小伙伴看出点门道,务必评论指点迷津)。如果有剩余空间,就依次往其中添加‘src’字符串,直至‘dst’缓冲区填满,返回值是‘dst’原字符串长度与‘src’长度之和。相比较strncat,strlcat的优点是:(1)第三个参数大小直接带入‘dst’的大小,不需要计算剩余空间;(2)返回值可以用来判断是否发生字符串截断。

对以上源代码不理解的地方稍作修改,有了以下函数:

size_t strlcat(char *dst, const char *src, size_t dsize)
{const char *odst = dst;const char *osrc = src;size_t n = dsize;size_t dlen;/* Find the end of dst and adjust bytes left but don't go past end. */while (n-- != 0 && *dst != '\0')dst++;dlen = dst - odst;if (n == 0 && *dst == '\0'){return dlen + strlen(src);}while (*src != '\0') {if (n != 0) {*dst++ = *src;n--;}src++;}*dst = '\0';return(dlen + (src - osrc));    /* count does not include NUL */
}

上述两个源代码最早出现在FreeBSD系统的标准库函数中,可以在'http://ftp.openbsd.org/pub/OpenBSD/'官网中找到其实现。在Window或者Censos标准库中没有其实现方式。windows使用strcpy_s、strncpy_s。

其他

如果编译平台是多个的话,由于strlcpy和strncpy_s的平台局限性,我们可以编写函数实现类似的功能,或者使用NSPR库来实现跨平台编译。NSPR库的知识和下载方式见‘https://www.jianshu.com/p/5e3d762981dd’。

转载于:https://www.cnblogs.com/yichi/p/10279257.html

C语言strcpy,strncpy和strlcpy讲解相关推荐

  1. C语言中函数strcpy ,strncpy ,strlcpy的用法

    C语言中函数strcpy ,strncpy ,strlcpy的用法 http://hi.baidu.com/qi_hao/blog/item/043ef21c6e26c58286d6b678.html ...

  2. Linux C中strcpy , strncpy , strlcpy 的区别

    strcpy ,strncpy ,strlcpy的用法 好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 1. s ...

  3. strcpy ,strncpy ,strlcpy地用法

    strcpy ,strncpy ,strlcpy地用法 好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 1. s ...

  4. strcpy()、strncpy()、strlcpy()、strncpy_s()函数

    strncpy()函数 原型:extern char *strncpy(char *dest, char *src, int n);     用法:#include <string.h>  ...

  5. C语言strcpy库函数的讲解

    C语言strcpy库函数的讲解 附1:MSDN关于strcpy库函数的简介 思路: 1.从上面的MSDN关于strcpy库函数的简介中,我们可以知道,传进函数的第一个参数是目标数组,也就是用来接收被拷 ...

  6. c语言 字符串 strncpy,详解c语言中的 strcpy和strncpy字符串函数使用

    详解c语言中的 strcpy和strncpy字符串函数使用 strcpy 和strcnpy函数--字符串复制函数. 1.strcpy函数 函数原型:char *strcpy(char *dst,cha ...

  7. (C语言)常用的字符串函数介绍(strcpy,strncpy,strcat,strncat,strcmp,strncmp,strchar,strlen)非常详细

    理解 strcpy,strncpy,strcat,strncat,strcmp,strncmp,strchar,strlen这些函数,可以帮助我们更好的对字符串进行操作,做到玩转字符串. 目录 1.s ...

  8. c语言strcpy两字符串长度不同,C语言 strcpy和memcpy区别详细介绍

    C语言 strcpy和memcpy区别详细介绍 PS:初学算法,开始刷leetcode,Rotate array的预备知识(写的代码Time Limit Exceed难过)于是百度高效算法,本篇作为预 ...

  9. C语言strcpy、strcnpy函数详解

    C语言strcpy.strcnpy函数详解 一.strcpy函数 1.函数原型 2.参数.返回值解析 3.注意事项 4.strcpy函数模拟实现 二.strncpy函数 1.函数原型 2.与strcp ...

最新文章

  1. djaogo知识点 python_python Django知识点总结
  2. NLPCC:预训练在小米的推理优化落地
  3. WebCore中的渲染机制(一):基础知识
  4. 云原生时代的 YAML 教程
  5. JDownloader 2 for Mac(不限速下载工具)
  6. 实用供热空调设计手册_暖通空调设计与施工数据图表手册
  7. 台式电脑怎么调分辨率_台式机屏幕分辨率不能调节怎么办
  8. android 简单的exoplayer全景播放器
  9. 【智能金融】黑科技让银行们“长牙齿”,是该让马云颤抖了!
  10. 通用模块系列--日期操作工具类
  11. 多传感器融合方式分析
  12. 符号链接symlink_什么是符号链接或符号链接? 如何为Windows和Linux创建Symlink?
  13. EasyNVR流媒体直播之:零基础实现摄像头的全平台直播 (一)内网直播的实现...
  14. passive-interface 总结整理
  15. 批处理 统计多个文件数量大小
  16. 程序员最该买的十本书
  17. netterm连接linux虚拟机(转)
  18. Android开发真实谎言:个人无空间nbsp;…
  19. 谷歌SEO中PBN外链是否值得做
  20. 2004年美国二十五所最热门高校大盘点

热门文章

  1. 别瞎操心了!机器人根本不会抢你的饭碗
  2. jquery初始化的三种方式
  3. P1772 [ZJOI2006]物流运输
  4. 图像热点(图像地图)
  5. delphi中等待外部应用程序执行完成后,再继续执行自有代码段
  6. 通过修改程序解决Vista/Win7/Win8下应用程序兼容性问题
  7. 在WinForm中使用Web Services 来实现软件自动升级(转)
  8. 关于xilinx fir use reloadable coefficient的用法
  9. verilog中的三目运算符
  10. VC中使用Matlab Engine出现无法找到libeng.dll的问题