小函数,大问题

对于strcpy,sprintf,strcat这些不安全的函数的讨论应该算是不少了,大家都知道可以用strncpy,snprintf和strncat来替换它们。但实际工作中似乎这些“安全版”的函数带来的疑惑还是不小,总结了一下,列在这里。

1.sprintf(char * str, const char * format, …) -> snprintf(char * str, size_t size, const char * format, …)

vsprintf(char * str, const char * format, va_list ap) -> vsnprintf(char * str, size_t size, const char * format, va_list ap)

按照C/C++标准,snprintf和vsnprintf永远不会往str中输出多于size所指定的字节数的字符(包括结尾的’/0’),它们也保证了一定会在str中写入’/0’,所以在使用这个函数后不用担心结尾的问题。

举例:

char buf[5];
snprintf(buf, 5, "This is a test string."); // buf becomes "This", buf[4] is '/0'
snprintf(buf, 6, "This is a test string."); // ERROR: buffer overflow
snprintf(buf, 5, "abc"); // buf becomes "abc", the value of buf[3] is '/0', buf[4] is undefined.

然而,VC中的_snprintf函数并没有按照这样的规定来做,它在输出缓冲区不够大时就不会输出结尾的’/0′(跟strncpy的行为类似)。所以要让上面的程序工作正常,必须做相应的修改。

char buf[5];
_snprintf(buf, 5, "This is a test string.");// buf becomes "This ", buf[4] is ' '
buf[4] = 0; // buf[4] is '/0' now.
_snprintf(buf, 6, "This is a test string."); // ERROR: buffer overflow
_snprintf(buf, 5, "abc"); // buf becomes "abc", the value of buf[3] is '/0', buf[4] is undefined.

如果要保证可移植性,就得按VC的写法,做一次对于标准来说很多余的“填0”的操作,再通过定义宏来区别平台分别选用snprintf或_snprintf。

2. strcat(char * dest, const char * src) -> strncat(char * dest, const char * src, size_t n);

这个函数比较简单,它保证不会写入多于n+1个字符,并且保证最后一定以’/0’结束。

举例:

char dest[5] = "abc";
strncat(dest, "defghijklmn", 5 - 3 - 1); // dest becomes "abcd", dest[4] is '/0',
// always minus the buffer length by 1 as the value of n.
strncat(dest, "defghijklmn", 5 - 3); // ERROR: buffer overflow

3. strcpy(char * dest, const char * src) -> strncpy(char * dest, const char * src, size_t n);

strncpy是一个比较容易出错的函数,它保证复制src中不多于n字节的内容,但是如果src的前n个字节中没有包含’/0’,就会导致 dest没有正常的以’/0’终止。另外,它还保证如果src的长度小于n,则dest剩余的部分都会以’/0’填充。在用这个函数时,有一个最佳实践就 是先把dest以0填充,并传入dest的长度减1的值作为n,这样可以确保安全。

举例:

  char buf[5] = {0}; // Always zero-fill the buffer,
// always use this form to initialize the stack arrays to get
//better performance over memset.
char * buf2 = new char[5];
memset(buf2, 0, 5); // Always zero-fill the buffer
strncpy(buf, "abcde", 5); // ERROR: buf is not null-terminated.
strncpy(buf2, "abcde", 5 - 1); // Right, always minus the buffer length by 1
//as the value of n. buf becomes "abcd", buf[4]
//is '/0' by initilization;
// This is a common error in the existing code.
char buf3[5];
const char * str = "Test";
strncpy(buf3, str, strlen(str)); // Wrong, buf may not null-terminated,
// potential buffer-overflow
strncpy(buf3, str, strlen(str)+1); // Wrong, potential buffer-overflow.
//No difference with using strcpy.

小函数,大问题》上有3条评论

  1. 同意楼上观点。
    不过在处理一些legacy的代码或者是纯C的代码的时候,不得不跟这些函数打交道,尤其是如果是为了修复一些静态检查工具对代码的告警的时候,就不得不把这些“不安全”函数一一用相应的“安全”函数进行改写,这时如果脑子不够清楚,很容易就会在程序中引入新的bug。

  2. hehe, 如果在Window的环境中用C++ 写应用程序,可以使用platform sdk中的strsafe.h的字符串函数。如果工程中包含了strsafe.h头文件,当你使用不安全的字符串函数时会有compile error. 非常的方便!http://msdn.microsoft.com/en-us/library/ms647466.aspx

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据