C语言strcpy,strncpy和strlcpy讲解 您所在的位置:网站首页 strcpy结束符也复制吗 C语言strcpy,strncpy和strlcpy讲解

C语言strcpy,strncpy和strlcpy讲解

2024-04-30 00:10| 来源: 网络整理| 查看: 265

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’。

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有