sprintf
_snprintf(snprintf)
std::stringstream
std::strstream
boost::lexical_cast
boost::format
Cstring::Format
1.Sprintf
使用sprintf 不安全,轻则破坏数据的准确性,重则程序崩溃。请看下面的程序片段:
CString strTest = "hello";
1 int a = 10;
2 char tbuf[10];
3 sprintf(tbuf, "%s,%d", "0123456789 0123456789 0123456789 0123456789",a);
4 printf("%s/n", tbuf);
5 if (10 == a)
6 strTest.Insert(0, "abc");
7 printf("%s/n", strTest.GetBuffer(0));
编译一下,没问题,0 error(s), 0 warning(s)。
看看输出来的结果,很幸运,执行第4行输出tbuf格式的字符串,跟想要的结果一致的。但奇怪的问题出现了,a的数值只有初始化赋值为10,没其他地方给它赋值了,为声明不会执行第6行,更奇怪的是执行第7行抛异常了。其实原因就是,sprintf,在幕后偷偷的修改了a的值,并且破坏了strTest这个对象。
类似的错误还有想这样的代码:
int i, a[10];
for (i=0; i<=10; ++i)
a[i] = 0;
本来想给数组a初始化,没想到的是,程序进入了一个死循环。
i (a[10]) | 0x0012ff54 |
a[9] | 0x0012ff50 |
a[8] | 0x0012ff4C |
a[7] | 0x0012ff48 |
… | … |
通常情况下,栈是往内存的低地址方向增长的,也就是说,现压栈的内容存放在高地址区域,后压栈的内容存放在低地址区域。
2._snprintf
把sprintf处的代码改为用_snprintf,看看结构如何。_snprintf(tbuf,sizeof(tbuf)-1, "%s,%d", "0123456789 0123456789 0123456789 0123456789",a);
我们一看结果就知道是缓冲区不够了,这样容易发现问题,也不会破坏程序内存。注意,即便这样,仍然还存在另外一种出错的可能,即调用者将缓冲区的长度搞错了。
3.Std::stringstream
ostringstream temp;
temp<< "0123456789 0123456789 0123456789 0123456789,"<<a;
string s = temp.str()
sprintf 和 std::stringstream的优缺点:
1).sprintf 具有易用性和清晰性,而std::stringstream 比较臃肿了,至少要三行代码实现。
2).Sprintf 字节利用缓冲区,效率高。而std::stringstream要尽管多次拷贝,有可能还要多次动态分配内存。
3).std::stringstream 动态分配内存,所以长度是安全的,不会出现破获内存的情况
4).std::stringstream 模版类型安全,方便。而sprintf有可能把一个整数格式到 %s
4.std::strsteam
char tbuf[10];
std::strstream temp(tbuf, sizeof(tbuf));
temp<< "0123456789 0123456789 0123456789 0123456789,"<<a<<’/0’;
效果跟_snprintf差不多,但是它要自己手动加上 ‘/0’ 字符串结束符。而且至少要写两行代码。优点也是长度安全,类型安全,有模版的亲和性。我个人来说不喜欢用。
5.boost::lexical_cast
string s = "0123456789 0123456789 0123456789 0123456789,"+boost::lexical_cast<string>(a);
实际上lexical_cast的目的只是为了将数据从一个可流化的类型转换为另一个可流化的类型。就类似atoi, itoa, 等之类的函数。做格式化一串数据感觉就不是很好了。
6.boost::format
boost::format fs = boost::format("%1%,%2%") %"0123456789 0123456789 0123456789 0123456789" % a;
//string ss = fs.str();
boost::format简洁优雅,长度安全,类型安区。只是由于内存是动态分配的,效率上比sprintf, _snprintf稍低一点。但如果我们的项目使用了boost库,用boost::format总体上比sprintf,_snprintf 都好一点。
7MFC中的CString
如果决定用mfc库,并且以后不会移植的话,最好就用Cstring。
CString s;
s.Format("%s,%d", "0123456789 0123456789 0123456789 0123456789", a);
但是它要表明格式的类型和sprintf, _snprintf一样,类型并不是总是安全的。
总结:通常情况下考虑效率使用_snprintf最好,如果使用boost库,且对运行效率没有苛刻的要求请使用boost::format, 如果使用了MFC,且又不考虑以后的移植,请使用Cstring。
补充:我们经常对字符串的拷贝同样有两c标准函数:
char *strcpy( char * strDest, const char *strSource );
char *strncpy( char *strDest, const char *strSource, size_t count );
万一我们的strDest缓冲区长度不够,同样会破坏内存,使程序崩溃。
例如:
char tbuf[10]={0};
string strSrc = "hello, I'm string";
strcpy(tbuf, strSrc.c_str());
改为
strncpy(tbuf, strSrc.c_str(), min(sizeof(tbuf)-1,strSrc.size()));
那就安全了。
上面这些都是一些自己在开发过程中的总结和心得,希望对朋友们有帮助!