前言

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. Webdriver之API详解(2)

    前言:今天继续上一篇文章https://www.cnblogs.com/linuxchao/p/linuxchao-selenium-apione.html分享selenium' webdriver ...

  2. June. 25th 2018, Week 26th. Monday

    Change in all things is sweet. 有改变就会有美好. From Aristole. Change is always good, but embracing change ...

  3. MyEclipse 的智能提示设置 使开发写代码的速度更快

    MyEclipse 是学习java 的人 最经常使用的IDE ,经常看到非常多人写代码的速度非常快.事实上他们大多数都使用了代码提示功能,代码提示有好有坏,好的方面就是使在项目开发中速度更快,不用去记 ...

  4. WEB框架-Django框架学习(一)-基础层级关系

    今日主要内容为Django架构,主要有路由层,视图层和模板层 1. Django的路由层 URL配置(URLconf)就像Django 所支撑网站的目录.它的本质是URL与要为该URL调用的视图函数之 ...

  5. 转://oracle 软件的收费模式

    Oracle软件本身是免费的,所以任何人都可以从Oracle官方网站下载并安装Oracle的数据库软件,收费的是License,即软件授权,如果数据库用于商业用途,就需要购买相应Oracle产品的Li ...

  6. [Alpha阶段]项目展示博客

    目录 Alpha阶段项目展示 1.团队成员介绍 2.工程相关信息 (1)我们的用户 (2)产品表现 (3)团队分工 (4)项目管理 (5)测试 (6)文档 (7)用户调研 3.项目信息 (1)实际进展 ...

  7. Spring Boot与分布式

    ---恢复内容开始--- 分布式.Dubbo/Zookeeper.Spring Boot/Cloud 一.分布式应用 在分布式系统中,国内常用zookeeper+dubbo组合, 而Spring Bo ...

  8. ccpc杭州站 赛后总结

    Ccpc杭州站赛后总结 2017年11月4号五号,我参加了ccpc杭州站的比赛,我的队友是聂少飞和王艳,在4号一点半,举行了比赛开幕式,听着教练代表的发言,听着参赛选手代表的发言,听着志愿者的发言,都 ...

  9. SpringMVC实现文件下载时,请求路径中的扩展名被省略

    问题描述 问题是这样的,我写了一个DownloadController,用来处理下载请求,预期效果如下: 客户端浏览器在访问URL -->   http://localhost:8080/ssm ...

  10. Vue.js 2.x笔记:组件(5)

    1. 组件简介 组件(Component)是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码. 组件:为了拆分Vue实例的代码量,以不同的组件来划分不同的功能模块,需要 ...