C语言strcpy,strncpy和strlcpy讲解
前言
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讲解的更多相关文章
- (C)strcpy ,strncpy与strlcpy
1. 背景 好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 2. strcpy strcpy 是依据 /0 作为 ...
- (C)strcpy ,strncpy和strlcpy的基本用法
好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 1. strcpy strcpy 是依据 /0 作为结束判断的, ...
- C语言中函数strcpy ,strncpy ,strlcpy的用法【转】
转自:http://blog.chinaunix.net/uid-20797562-id-99311.html strcpy ,strncpy ,strlcpy的用法好多人已经知道利用strncpy替 ...
- C语言中函数strcpy ,strncpy ,strlcpy的用法
strcpy ,strncpy ,strlcpy的用法 好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 1. s ...
- Linux C中strcpy , strncpy , strlcpy 的区别
strcpy ,strncpy ,strlcpy的用法 好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 1. s ...
- C语言 - strcpy和strncpy的编程实现及总结
一.字符串的strcpy与strncpy函数 1.编程实现strcpy函数(笔试很容易考到) 要求: 原型:char *stpcpy(char *strDest,char *strSrc); 头文件: ...
- C语言——常用标准输入输出函数 scanf(), printf(), gets(), puts(), getchar(), putchar(); 字符串拷贝函数 strcpy(), strncpy(), strchr(), strstr()函数用法特点
1 首先介绍几个常用到的转义符 (1) 换行符“\n”, ASCII值为10: (2) 回车符“\r”, ASCII值为13: (3) 水平制表符“\t”, ASCII值为 9 ...
- strcpy、strncpy、strlcpy的区别
- 电脑小白学习软件开发-C#语言基础之循环重点讲解,习题
写代码也要读书,爱全栈,更爱生活.每日更新原创IT编程技术及日常实用视频. 我们的目标是:玩得转服务器Web开发,搞得懂移动端,电脑客户端更是不在话下. 本教程是基础教程,适合任何有志于学习软件开发的 ...
随机推荐
- 关于nginx unit服务非正常关闭后,无法重新启动问题的处理
昨天在前领导技术大牛吕哥的帮忙下,python服务管理从nginx+supervisor+uwsgi+python3改为了轻便结构nginx + unit + python3,部署和配置起来顿时轻松起 ...
- R语言学习——图形初阶之折线图与图形参数控制
plot()是R中为对象作图的一个泛型函数(它的输出将根据所绘制对象类型的不同而变化):plot(x,y,type="b")表示将x置于横轴,y置于纵轴,绘制点集(x,y),然后使 ...
- Python--day07(数据类型转换、字符编码)
昨天内容回顾 1. 深浅拷贝: 值拷贝:直接赋值,原列表中任何值发生改变,新列表的值都会发生改变. 浅拷贝:通过copy()方法,原列表中存放值的地址没有发生改变,但内部的值发生改变,新列表也随之改 ...
- windows系统中给qt工程添加第三方库
· TEMPLATE = app CONFIG += console c++11 CONFIG -= app_bundle CONFIG -= qt SOURCES += main.cpp LIBS ...
- Playfair 加密
题目真的好长但是意思很简单 89.加密 (15分)C时间限制:3 毫秒 | C内存限制:3000 Kb题目内容:一种Playfair密码变种加密方法如下:首先选择一个密钥单词(称为pair)(字母不重 ...
- 滴滴 CTO 架构师 业务 技术 战役 时间 赛跑 超前 设计
滴滴打车CTO张博:生死战役,技术和时间赛跑-CSDN.NEThttps://www.csdn.net/article/2015-06-25/2825058-didi 滴滴出行首席架构师李令辉:业务的 ...
- 运行错误:Application Error - The connection to the server was unsuccessful
在模拟器上上启动ionic4.6版本 打包成的android APK,启动了很久结果弹出这个问题: Application Error - The connection to the server w ...
- Python基础:编码规范(4)
1.命名规范 Python中不同代码元素采用不同命名方式: ◊ 包名:全部小写字母,中间可以由点分隔开.作为命名空间,包名需具有唯一性. ◊ 模块名:全部小写字母,如果是多个单词构成,使用下划线分隔. ...
- Vue.js 2.x笔记:安装与起步(1)
1. 环境准备 Vue是一套用于构建用户界面的渐进式框架,设计为可以自底向上逐层应用.Vue 的核心库只关注视图层. 安装Node.js,下载:https://nodejs.org/ 查看安装: $ ...
- mysql-笔记-numberic 方法 操作符
DIV 整数除法---结果舍去小数部分, /除法 ---除以0时返回 null值 -减法 % MOD 取模 ---结果为 余数 也可以用于有小数部分的数返回余数,mod(N,0)返回null值 + 加 ...