在做一个大型系统的性能优化时,字符串拷贝都能在性能消耗方面占据相当比重,选择合适的字符串处理函数,至少能避免一些无谓的性能消耗。通常接触的字符串拷贝函数有这么些:snprintf strncpy memcpy strlcpy
。考虑的核心问题就是高效和安全。
int snprintf(char *str, size_t size, const char *format, ...);
通过snprintf(dst_str, dst_size, "%s", source_str);
可以将source_str拷贝到dst_str。snprintf的特点是安全,不管怎么着,它都能保证结果串str以\0结尾,哪怕dst_size不够大,它都能做好截断,同时在末尾添加上\0。在性能方面,当source_str远长于dst_size时,该函数却很低效的,其他情况下该函数即安全又高效。之所以当source_str远长于dst_size时,该函数低效,是因为该函数的返回值为source_str的长度,那么它就必须将source_str全部过一遍,哪怕并不拷贝到dst_str中去。注意,当source_str长度比dst_size小时,它不会把末尾的多余字符置零,所以它是很高效的,不会多做无用功。
char *strncpy(char *dest, const char *src, size_t n);
strncpy比strcpy安全稍微安全点,strncpy顶多拷贝n个字节,所以在dest不够的情况下,不会像strcpy出现拷贝越界。但也只是稍微安全点而已,如果src长度比n大,那么dest就不是以\0结尾的了,所以通常要这么使用:
strncpy(buf, str, n);
if (n > 0) {
buf[n - 1]= '\0';
}
在高效方面strncpy也做得不好,它的大致实现这样的:
char *strncpy(char *dest, const char *src, size_t n)
{
size_t i;
for (i = 0; i < n && src[i] != '\0'; i++) {
dest[i] = src[i];
}
for ( ; i < n; i++) {
dest[i] = '\0';
}
return dest;
}
也就是说,当src长度比n小时,未使用的字符,它会帮你填充为0,但我们往往会开一个大的Buffer来接收数据,如此必然有大量的多余空间,这样不仅速度慢,而且还会导致实存的消耗上涨。总之,strncpy的即不怎么安全,也不怎么高效。
void *memcpy(void *dest, const void *src, size_t n);
很明显memcpy不安全,它只是做简单的拷贝而已,不过它很高效,如果你能保证不会越界的话,就使用memcpy吧。之前跟一些人沟通,居然有人以为memcpy的高效是因为使用什么特别的内核函数或者硬件指令,看样子还是有人对memcpy不理解,所以会想太多,其实memcpy的实现就是一个一个赋值,strcpy也是一个一个赋值,不过,memcpy做了点优化,64位机器下,它会64位64位地拷贝,即8个字节8个字节地拷贝,对于除8的余数,再做单独处理,而strcpy是一个字节一个字节地拷贝,所以当有大量字符串需要拷贝时,理论上memcpy会比strcpy快8倍。如果用sse sse2之类的128位指令来实现,可以拷贝得更快,实现起来也不难,但gnu std标准库得做得通常,不可能去考虑各种高端CPU来写代码。
size_t strlcpy(char *dst, const char *src, size_t size);
某些平台上会有strlcpy的库函数,但是咱正常使用的gcc库是没有这个函数的,其实就是对snprintf和strncpy的缺陷做了弥补,即保证安全,又保证高效。就不讲的。
其实理解好snprintf和strncpy本身的特点,就够用了,假如能确保安全,那么使用strcpy或者memcpy都是更好的选择。总之,理解清楚原理才是最重要的。