C标准库函数strncpy用法解析
来源:互联网 发布:mysql查看数据库表 编辑:程序博客网 时间:2024/06/06 02:10
C库函数中,使用的最频繁的莫过于string库中的strcpy和strncpy这两个函数了,很多场合都需要用到,但是对这两个函数的使用上,还是存在很多问题,本文整理下这个点,并结合标准库的实现来说明C语言编程中可能存在的陷阱。
1. strcpy:
下面是从《C标准库》中摘录的对这两个函数的说明:
strcpy()函数:
概述:
#include <string.h> char * strcpy(char *s1, const char *s2);
说明:
函数strcpy把s2指向的串(包含终止的空字符)复制到s1指向的数组中,如果复制发生在两个重叠的对象中,则行为未定义。
返回值:
函数strcpy返回s1的值。
通过上面的描述,我们可以得到以下三点:
a. strcpy不会对地址是否发生重叠进行检测,如果可能有重叠的部分,这个接口的行为是未定义的,这个属性制约了该接口的使用场景
b. strcpy不会检测目的串的空间能否容纳下源串,所以使用这个接口的时候,必须保证目的串的存储空间足以容纳下源串
c. 返回值是源串的地址,而不是一个size_t的值,来描述真实发生的拷贝字符个数
本文主要关注第二点,从函数说明中可以看到, strcpy执行的真实拷贝长度是s2.len+1,既然strcpy不能保证目的存储空间一定能够容纳下源串,那么自然而然就想到了strncpy函数.
2.strncpy:
strncpy()函数:
概述:
#include <string.h> char *strncpy(char *s1, const char *s2, size_t n);
说明:
函数strncpy从s2指向的数组中复制最多n个字符(不复制空字符后面的字符)到s1指向的数组中。如果复制发生在两个重叠的对象中,则行为未定义。
如果s2指向的数组是一个长度比n短的字符串,则在s1指向的数组后面添加空字符,直到写入了n个字符。
返回值:
函数strncpy返回s1的值。
从上面的描述可以发现,这个函数的行为和strcpy有比较大的差别,虽然能够通过设置n的值为s1对应的存储空间来弥补strcpy的不足,但是这个函数的行为却比strcpy复杂了很多,这个函数的行为取决于strlen(s2)和n的大小关系:
if strlen(s2) < n:
这个函数执行两部分操作,先拷贝全部的s2的内容到s1,然后把strlen(s2) – n的长度用空字符填满,后面这个操作存在潜在的性能问题。
if strlen(s2) >= n:
这个函数拷贝s2的前n个字符到s1中
从上面的分析可以得到结论:strncpy不会一直给s1的结尾补上空字符,这个点是这个函数被误用的根源,比如像下面的代码:
char dst[8] = “abcdefg”;char src[16] = “ABCDEFGHIJKLMN”;strncpy(dst, srs, 8);
这个时候,dst的内容按次序分别为ABCDEFGH,结尾不是空字符,这个时候,如果只是读取dst的内容,则一般情况下不会导致程序崩溃,如果根据dst的空字符来控制数据写入,则会引起缓冲区溢出问题,会破坏掉dst地址增长方向的内存空间,引起不可预知的错误,具体发生哪类错误取决于dst所在的地址空间中的具体位置。
下面是在Ubuntu 16.04.2 LTS (GNU/Linux 4.8.0-36-generic x86_64)的测试结果。
程序源代码:
#include <stdio.h>#include <string.h>#define DST_LEN (8)#define SRC_LEN (16)int test_strncpy(){ char dst[DST_LEN] = "abcdefg"; char src[SRC_LEN] = "ABCDEFGHIGKLMN"; strncpy(dst, src, DST_LEN); size_t len = strlen(dst); printf("The dst len is %u!\n", len); printf("The dst string: %s!\n", dst); return 0;}int main(){ test_strncpy(); return 0;}
执行结果如下:
The dst len is 11!
The dst string: ABCDEFGHM@!——– H后面的值是随机的,H代替了应该出现的空字符。
从这个测试可以看到,这个函数使用上具有一定的迷糊性:不会一直在结尾补空字符,完全取决于strlen(src)和n的关系。
上面的这种情形下, dst退化成字符数组,不再具有C字符串的行为,为了使程序能够正常工作,修改程序为只拷贝前面的7个字节,数组的最后一个字节设置为空字符,这样程序就能正常工作。
修改后的test_strncpy函数:
int test_strncpy(){ char dst[DST_LEN] = "abcdefg"; char src[SRC_LEN] = "ABCDEFGHIGKLMN"; strncpy(dst, src, DST_LEN - 1); // use DST_LEN - 1 instead dst[DST_LEN - 1] = '\0'; // guarantee NUL-terminate size_t len = strlen(dst); printf("The dst len is %u!\n", len); printf("The dst string: %s!\n", dst); return 0;}
执行结果如下:
The dst len is 7!
The dst string: ABCDEFG!
通过这个例子,可以看到,使用strncpy的时候,必须牢记这个接口不会保证最后的字符是空字符,下面是来自《C标准库》的源代码,从源代码,我们可以看到函数的实现细节,正好印证了测试程序中的行为。
char * (strncpy)(char *s1, const char *s2, size_t n){ char *s; for (s=s1; 0 < n && *s2 != ‘\0’; --n) *s++ = *s2++; for (; 0 < n; --n) *s++ = ‘\0’; return (s1);}
从函数的实现可以看到,strncpy可以保证目的串能够容纳源串,但是不能保证拷贝后目的串还是标准的C字符串(末尾的空字符0有可能被覆盖),同时引入了性能问题,如果源串小,目的串的存储空间比较大,strncpy的效率不如strcpy。为了解决这些问题,openbsd引入了函数strlcpy.
3. strlcpy
strlcpy()函数:
概述:
size_t strlcpy(char *dst, const char *src, size_t size);
说明:
函数strcpy把s2指向的串(包含终止的空字符)复制到s1指向的数组中,最多复制size个字符,如果第size-1个字符不是空字符,则设置dst中的size-1个字符为空字符;如果复制发生在两个重叠的对象中,则行为未定义。
如果str的长度小于size,则不会在dst后面写入空字符。
返回值:
函数返回src的长度。
这个函数弥补了strcpy和strncpy的问题,同时返回了src的长度,通过这个长度和拷贝后的dst的长度进行比较,可以判断是否发生截断,以方便后续程序处理。这个函数没有进入标准库,在linux下也没有对应的版本,但是在BSD系统中广泛存在,也有很多开源代码在使用,甚至进入了linux内核,但是到目前为止,还未进入glibc库和C标准库,至于这后面的原因,需要继续探索,这篇文章给出了一个参考:strlcpy and strlcat - consistent, safe, string copy and concatenation.
下面代码是openbsd对应的源代码:
#include <sys/types.h>#include <string.h>/* * Copy string src to buffer dst of size dsize. At most dsize-1 * chars will be copied. Always NUL terminates (unless dsize == 0). * Returns strlen(src); if retval >= dsize, truncation occurred. */size_tstrlcpy(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 */}DEF_WEAK(strlcpy);
通过对strncpy函数的剖析,对标准库的理解需要深入到实现细节这一步,才能驾轻就熟地使用它们,这也是高质量C编程的基石。这篇文章重点在防止buff overflow上,地址重叠和性能问题,后面的文章会继续讨论。
参考:
《C标准库》(美.P.J.Plauger)
《C primer plus the 5th》(Prata S)
https://en.wikipedia.org/wiki/C_string_handling#strlcpy
- C标准库函数strncpy用法解析
- C部分标准库函数用法
- strncpy ( )【C语言库函数源代码】
- C 中标准库函数 qsort 的用法
- C库函数之strcpy,strncpy,memcpy
- 模拟实现C库函数strncpy、strncat、strncmp
- 库函数strncpy
- C标准库函数浅析
- C标准库函数(a)
- .调用标准C库函数
- c标准库函数
- C标准库函数浅析
- c标准库函数列表
- C标准库函数
- C: 标准库函数 floor
- C标准库函数
- 标准C库函数包括:
- C 标准库函数
- Bitmap保存到SD
- 2D图形变换原理浅析
- POJ 3252 Round Numbers【数位dp+lead使用】
- Linux网路编程网络基础之网络‘协议的概念
- Redhat7.2和Redhat6.5配置本地yum源和网络yum源
- C标准库函数strncpy用法解析
- html学习2
- 集合(二)
- 【17/8】android按钮的四种点击事件
- 常用的mysql语句
- MySQL主从架构配置
- ios10之后本地通知
- 临界区的LockCount为何小于-1
- 用Python处理csv文件