前言

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[];
memset(dest, 0x0, sizeof(dest));
char src[] = ""; 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 != ) {
while (--nleft != ) {
if ((*dst++ = *src++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src. */
if (nleft == ) {
if (dsize != )
*dst = '\0'; /* NUL-terminate dst */
while (*src++)
;
}
return(src - osrc - ); /* 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-- != && *dst != '\0')
dst++;
dlen = dst - odst;
n = dsize - dlen; if (n-- == )
return(dlen + strlen(src));
while (*src != '\0') {
if (n != ) {
*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-- != && *dst != '\0')
dst++;
dlen = dst - odst; if (n == && *dst == '\0'){
return dlen + strlen(src);
}
while (*src != '\0') {
if (n != ) {
*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’。

C语言strcpy,strncpy和strlcpy讲解的更多相关文章

  1. (C)strcpy ,strncpy与strlcpy

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

  2. (C)strcpy ,strncpy和strlcpy的基本用法

    好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 1. strcpy strcpy 是依据 /0 作为结束判断的, ...

  3. C语言中函数strcpy ,strncpy ,strlcpy的用法【转】

    转自:http://blog.chinaunix.net/uid-20797562-id-99311.html strcpy ,strncpy ,strlcpy的用法好多人已经知道利用strncpy替 ...

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

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

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

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

  6. C语言 - strcpy和strncpy的编程实现及总结

    一.字符串的strcpy与strncpy函数 1.编程实现strcpy函数(笔试很容易考到) 要求: 原型:char *stpcpy(char *strDest,char *strSrc); 头文件: ...

  7. C语言——常用标准输入输出函数 scanf(), printf(), gets(), puts(), getchar(), putchar(); 字符串拷贝函数 strcpy(), strncpy(), strchr(), strstr()函数用法特点

    1 首先介绍几个常用到的转义符 (1)     换行符“\n”, ASCII值为10: (2)     回车符“\r”, ASCII值为13: (3)     水平制表符“\t”, ASCII值为 9 ...

  8. strcpy、strncpy、strlcpy的区别

  9. 电脑小白学习软件开发-C#语言基础之循环重点讲解,习题

    写代码也要读书,爱全栈,更爱生活.每日更新原创IT编程技术及日常实用视频. 我们的目标是:玩得转服务器Web开发,搞得懂移动端,电脑客户端更是不在话下. 本教程是基础教程,适合任何有志于学习软件开发的 ...

随机推荐

  1. centos7 最小安装初始化

    配置阿里yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup \&&cu ...

  2. Linux Mint(ubuntu)如何汉化firefox浏览器?

    自从火狐浏览器改用新的Quantum新核心后,原来的一些插件.性能,还有一部分设置方法都与原来相比有所改变,比如汉化问题,以前的做法是这样的: sudo apt-get install firefox ...

  3. redux 简介

    概述 Redux 本身是个极其简单的状态管理框架, 它的简单体现在概念少, 流程明确. 但是, 正是因为简单, 使用上没有强制的约束, 所以如果用不好, 反而会让状态管理更加混乱. 我觉得, 用好 R ...

  4. RubyGems系列之创建自己的gem

    转载请注明来源:https://www.cnblogs.com/zhanggui/p/9720818.html 一. 前言 我们可以在rubygems.org中下载安装他人创建的gem.现在,我们尝试 ...

  5. @EnableFeignClients 注解

    feignClents在spring容器里找不到的原因 当使用的feignClents 来自引用别的工程时,需要指定包名,如果不指定就算使用ComponentScan 扫描也不行 import org ...

  6. Map根据value值进行倒序排列

    public List<Map.Entry<Integer,BigDecimal>> sortByMapValue(HashMap<Integer, BigDecimal ...

  7. P1177 【模板】快速排序 题解

    本质为sort,这里我们用优先队列(堆)解决. 什么是堆? 堆 堆是一个完全二叉树,而且是每层都有规律的二叉树 规律大概是: 小根堆:最上层数的大小最小,往下每层结点都比父亲结点大,比两个儿子结点小 ...

  8. linux-高并发与负载均衡-lvs-功能配置介绍

    百度百科: LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统.本项目在1998年5月由章文嵩博士成立,是中国国内最早出现的自由软件项目之一. ...

  9. C# call webservice方法

    https://www.cnblogs.com/Fooo/p/5507153.html

  10. tensorflow-RNN和LSTM

    本章主要介绍循环神经网络(recurrent neuralnetwork,RNN)和长短时记忆网络(long short-term memory,LSTM) 一. RNN简介 1.背景 循环神经网络挖 ...