今天在用sprintf和sprintf_s的過程中遇到了問題,想了挺久才解決的,現在就來記錄一下。
先上使用sprintf時出現錯誤的代碼
#include <new>
#include <string>
int main()
{
std::string tmp = "ABC";
char *p = new char[tmp.size()]();
for (std::size_t index = 0; index < tmp.size(); ++index)
{
sprintf(p + index, "%c", tmp[index]);
}
delete [] p;
return 0;
}
上面的代碼會在
delete [] p;
時報告出現越界的錯誤。
但是按照代碼的本意來說,就是應該只把ABC從tmp中移到了p,不會出現越界纔對的啊。於是我就在調試時看了一下內存的情況。
上面四幅圖是對應剛分配內存時、第一次循環時、第二次循環時、第三次循環時的內存狀態圖。
由第四幅圖可以看出,在第三次循環的時候,多了一個0x00,導致越界了,那麼這個0x00是從哪裏來的呢?看下圖
從上圖可以看出,"%c"包含了三個字符,%、c、\0,這樣的話就能知道越界的原因了,sprintf每次將%c\0複製到了緩衝區裏面去了,所以造成最後多了一個0x00,解決方法是只要在分配內存的時候分配多一個字節即可,代碼如下
#include <new>
#include <string>
int main()
{
std::string tmp = "ABC";
char *p = new char[tmp.size() + 1]();
for (std::size_t index = 0; index < tmp.size(); ++index)
{
sprintf(p + index, "%c", tmp[index]);
}
delete [] p;
return 0;
}
但是上述代碼中,如果將sprinf更換爲sprintf_s,越界情況還是會出現,那麼問題又出現在什麼地方了呢?同樣道理,先上代碼。
#include <new>
#include <string>
int main()
{
std::string tmp = "ABC";
char *p = new char[tmp.size() + 1]();
for (std::size_t index = 0; index < tmp.size(); ++index)
{
sprintf_s(p + index, tmp.size() + 1,"%c", tmp[index]);
}
delete [] p;
return 0;
}
上面的代碼會在
delete [] p;
時報告出現越界的錯誤。
於是我又在調試的狀態下去看了一下內存的情況。。。
同樣,上面四幅圖是對應剛分配內存時、第一次循環時、第二次循環時、第三次循環時的內存狀態圖。
從上述四張圖可以看出,sprintf_s函數會在賦值之後將未使用的內存填充爲0xfe,而未使用的內存就是由第二個參數減去賦值所佔有的內存長度得出的,但是它在計算內存的起點時是由你所填充的內存的位置決定的,於是就出現了上述圖片中的現象,41 00 fe fe,41 42 00 fe fe,41 42 43 00 fe fe,造成了越界。那麼怎麼修改上述代碼,修正越界的情況呢?只要修正每次賦值時第二個參數的值即可,代碼如下
#include <new>
#include <string>
int main()
{
std::string tmp = "ABC";
char *p = new char[tmp.size() + 1]();
for (std::size_t index = 0; index < tmp.size(); ++index)
{
sprintf_s(p + index, tmp.size() + 1 - index,"%c", tmp[index]);
}
delete [] p;
return 0;
}
看來spintf_s也有很多坑啊。。。
當然也可以用std中的ostringstream來實現sprintf的功能,boost中fmt就更不用說了,但是ostringstream在使用格式化操作的時候真的是。。。很麻煩,所以日常還是使用sprintf會多些。
在這裏也貼上一個使用ostringstream格式化操作的代碼。代碼如下
#include <iostream>
#include <sstream>
#include <iomanip>
int main()
{
int value = 500;
char sz[32] = { 0, };
//使用sprintf完成格式化的操作
sprintf(sz, "%08X", value);
std::cout << "sprintf: " << sz << std::endl;
//使用ostringstream完成格式化的操作
memset(sz, 0, sizeof(sz));
std::ostringstream oss;
oss << std::setw(8) << std::setfill('0') << std::hex << std::setiosflags(std::ios::uppercase) << value;
strcpy(sz, oss.str().c_str());
std::cout << "ostringstream: " << sz << std::endl;
return 0;
}
輸出的結果如下
雖然ostringstream比sprintf麻煩,但是ostringstream也比sprintf更安全。畢竟不用自己去管理內存了。
PS:上述全部代碼均在Visual Studio 2017上編寫。