size_t s1len = strlen (s1); // pass 1 over s1if (dsize <= s1len)s1len = dsize - 1; // no need to nul-terminatememcpy (d, s1, s1len); // pass 2 over s1size_t s2len = strlen (s2); // pass 1 over s2if (dsize - s1len <= s2len)s2len = dsize - s1len - 1;memcpy (d + s1len, s2, s2len); // pass 2, over s2d[s1len + s1len] = ''; // nul-terminate result3.使用sprintf和snprintf进行连接出于对代码复杂性和可读性的担心,程序员们有时会使用snprintf函数进行字符串连接 。
snprintf (d, dsize, "%s%s", s1, s2);这样做代码的可读性非常好,但是,由于snprintf的开销相当大,它的低效率导致它可能比使用字符串函数慢几个数量级 。snprintf的开销不仅是由于解析格式字符串,而且还由于格式化I/O函数实现中通常固有的复杂性 。
一些编译器(如GCC和Clang)试图通过将非常简单的sprintf和snprintf调用转换为strcpy或memcpy调用以提高效率,避免了对I/O函数的某些调用的开销(请参阅这个在线示例https://godbolt.org/z/RaWkyd) 。然而,由于C库中没有等价的字符串函数,而只有当snprintf调用被证明不会导致输出的截断时,转换才会完成,因此对snprintf的相应转换很少能够发生 。memcpy本身不合适,因为它复制的字节数与指定的字节数完全相同,strncpy也不适合,因为它把目标字符串的最后的NUL结束符之后的位数都覆盖了 。
由于字符串的冗余传递次数,将snprintf调用转换为strlen和memcpy调用序列产生的额外开销,也被视为得不偿失 。在这个页面上,标题为Better builtin string functions部分列出了GCC优化器在这方面的一些限制,以及改进它的一些折中措施 。
4.POSIX的stpcpy和stpncpy函数为了帮助解决这个问题,在过去很多年里出现了很多超出标准C的库解决方案 。POSIX标准包括stpcpy和stpncpy函数,这两个函数的实现方法是如果找到NUL结束符,则返回指向该字符的指针 。这些函数可以用来缓解上面提到的麻烦和低效率 。
const char* stpcpy (char* restrict, const char* restrict);const char* stpncpy (char* restrict, const char* restrict, size_t);特别是,在不考虑缓冲区溢出的情况下,可以像下面这样调用stpcpy来连接字符串:
stpcpy (stpcpy (d, s1), s2);然而,当字符串副本必须以目标大小为边界时,等效地使用stpncpy并不会消除将第一个NUL字符之后的剩余目标位置清零并直到边界指定的最大字符位置的开销 。
char *ret = stpncpy (d, dsize, s1); // zeroes out d beyond the end of s1dsize -= (ret - d);stpncpy (d, dsize, s2); // again zeroes out d beyond the end所以,这个函数仍然效率低下,因为对它的每次调用都会将目标中剩余的空间以及复制的字符串的末尾的空间清零 。因此,这个操作的复杂性仍然是二次方的 。效率低下的严重程度随着目标的大小成比例地增加,而与被连接的字符串的长度成反比增加 。
5.OpenBSD的strlcpy和strlcat函数为了应对针对strcpy和strcat函数的弱点以及上面讨论的strncpy和strncat的一些缺点的缓冲区溢出攻击,OpenBSD项目在20世纪90年代末引入了一对替代API(strlcpy和strlcat),旨在使字符串复制和连接更加安全(http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm) 。
size_t strlcpy (char* restrict, const char* restrict, size_t);size_t strlcat (char* restrict, const char* restrict, size_t);strncpy和strlcpy函数之间的主要区别在于返回值:前者返回指向目标的指针,后者则返回复制的字符数 。另一个区别是strlcpy函数总是在目标中只存储一个NUL结束符 。要连接s1和s2,可以按以下方式使用strlcpy函数:
size_t n = strlcpy (d, s1, dsize);dsize -= n;d += n;strlcpy (d, s2, dsize);这使得strlcpy在使用性和简单性方面都可以与snprintf相提并论(当然snprintf的开销虽然恒定,但要大得多) 。
除了OpenBSD以外,strlcpy和strlcat函数在其他系统上也可用,包括Solaris和linux(在BSD兼容库中) 。但是由于这些系统不是由POSIX指定的,所以这两个函数在那些系统中并不总是存在 。
6.POSIX的memccpy函数POSIX还定义了另一个函数memccpy,该函数具有上面讨论过的所有理想属性,可以用来解决上面的问题 。
void* memccpy (void* restrict dst, const void* restrict src, int c, size_t n);【在C语言中如何高效地复制和连接字符串?】这个函数结合了memcpy、memchr的特性以及上面讨论的API的最佳方面的特性 。
- 和memchr一样,它会扫描源序列以查找由其参数之一指定的字符的第一次出现 。字符可以是任何值,包括零 。
推荐阅读
- ”什么是内网穿透“详解
- 「C语言」常用算法
- 现在开淘宝网店是不是一定要交保证金 开淘宝店必须交保证金吗
- 总结Java中return语句的用法
- Mac平台上实现视频采集
- CentOS 8 安装图解
- 白鹿茶韵词语茶香与茶友起体验传统文化的魅力
- 中欧国际茶座交易中心在新疆阿拉山口开设
- 虾球烩豆腐
- 关于茶或促进糖尿病伤口愈合的研究表明茶多酚在其中发挥作用
