文章目录

  • ​​小结​​
  • ​​问题及解决​​
  • ​​参考​​

小结

使用​​sprintf(...)​​​进行格式化是一种标准的做法,但是这样做是有一个极大的风险,由于​​sprintf(...)​​​不进行边界检查,这样会有写操作溢出边界的风险,并导致程序崩溃。本文进行了简单写操作溢出边界的测试,模拟程序崩溃,并建议了更为安全的​​snprintf(...)​​方法进行格式化。

问题及解决

众所周知,​​sprintf(...)​​​不进行边界检查,再加上​​double​​​或者是​​float​​的内建的数据类型一起使用(有时候会是一个比较大的正数),比较容易出现写操作溢出边界并导致程序崩溃,已经是臭名昭著了。

以代码​​sprintf(str, "%.2fMB(%.2f%%)", double_a, double_b);​​为例。

​sprintf​​​的说明指示符​​%.2f​​​规定了小数点后面截取两位,但小数点前面有多少位就采用多少位,例如像​​2.4008127812204012E+159​​​这样的大数,基本上小数点前面就超过160位数字了。如果数组​​str​​比较小,就抓瞎了,写操作会溢出边界,并致程序崩溃。这种错误有时候极难定位,浪费很多宝贵的时候来找bug。

如果采用比较安全的做法, 例如: ​​snprintf(str_short, 30, "%.2fMB(%.2f%%)", double_a, double_b);​​,规定了边界,只写入前29位(最后再加一个结尾符),这样就不用担心写操作会溢出边界的问题了。

以下是程序测试很好地模拟了几种情况(​​double_a​​​是一个很大的数,​​double_b​​是一个很小的数):

char str[200];

char str_short[30];

double double_a= 2.4008127812204012E+159;
double double_b= 1.3906711615670009E-309;

cout << "double_a= " << double_a<< endl;
cout << "double_b= " << double_b<< endl;

//以下代码不会出现溢出边界,程序正常运行
sprintf(str, "%.2fMB(%.2f%%)", double_a, double_b);
cout << "str string = " << strStorage << endl;

//以下代码会出现溢出边界并导致程序崩溃, 注释之
//sprintf(str_short, "%.2fMB(%.2f%%)", double_a, double_b);
//cout << "str_short string = " << str_short<< endl;

//以下代码是比较安全的做法,进行有效的边界检查, 不会出现溢出边界,程序正常运行,输出了29位数字(后接一个结尾符)
snprintf(str_short, 30, "%.2fMB(%.2f%%)", double_a, double_b);
cout << "str_short string = " << str_short<< endl;

system("pause");
return 0;

输出结果:

double_a= 2.40081e+159
double_b= 1.39067e-309
str string = 2400812781220401246196118053255811918339999978374591715533604156445593861118215728144462510983065193819073699313679732935503602200085321608454731838025375940608.00MB(0.00%)
str_short string = 24008127812204012461961180532
Press any key to continue . . .

参考

​sprintf(str,“%f”, voltage_temp ); works in C but not C ++​​​​C++ Buffer Overflow: Format String (%f/%F)​​​​Stackoverflow: understanding the dangers of sprintf(…)​​​​C++ float and double​