需求:將檔案二進位內容以十六進位文字格式輸出(如 4D 5A 90 00 ...)。直覺寫法:讀檔 → 迴圈 wsprintf 格式化每位元組 → lstrcat 串接 → 寫檔。程式碼無語法錯誤、結果正確,但速度慢得驚人,進度條越跑越慢。
查看 CRT 原始碼:
char* __cdecl strcat(char* dst, const char* src) {
char* cp = dst;
while (*cp) cp++; // 尋找結尾 \0
while (*cp++ = *src++); // 複製 src
return dst;
}
每次 lstrcat 都必須從頭掃描尋找結尾。迴圈跑 10 萬次,前 1 萬字元被掃描 10 萬次 → 總操作量呈平方級增長。這就是進度條越跑越慢的真相。
void Save() {
// ... 讀檔、配置緩衝區 lpDst ...
DWORD j = 0; // 指向 lpDst 目前尾端
for (DWORD i = 0; i < dwSize - 1; i++) {
if (i % 16 == 15) { // 換行
wsprintf(szByte, "%02X ", lpbySrc[i]);
k = 4; // "XX " 長度
} else {
wsprintf(szByte, "%02X ", lpbySrc[i]);
k = 3; // "XX " 長度
}
lstrcpy(&lpDst[j], szByte); // 直接複製到尾端
j += k;
}
// 處理最後一位元組
wsprintf(szByte, "%02X", lpbySrc[i]);
lstrcpy(&lpDst[j], szByte);
// ... 寫檔、釋放 ...
}
避免重複掃描,速度提升數百倍。
MFC CString::ConcatCopy 使用 memcpy 而非 strcat,避免尋找結尾:
void CString::ConcatCopy(int nSrc1Len, LPCTSTR lpszSrc1Data,
int nSrc2Len, LPCTSTR lpszSrc2Data) {
int nNewLen = nSrc1Len + nSrc2Len;
if (nNewLen != 0) {
AllocBuffer(nNewLen);
memcpy(m_pchData, lpszSrc1Data, nSrc1Len * sizeof(TCHAR));
memcpy(m_pchData + nSrc1Len, lpszSrc2Data, nSrc2Len * sizeof(TCHAR));
}
}
但 CString::operator+= 內部呼叫 ConcatCopy,每次串接會重新配置緩衝區並複製兩次(舊資料 + 新資料),頻繁迴圈下效能同樣慘不忍睹。
strcat 必須掃描尾端,適合偶爾串接,不適合高頻迴圈。+= 觸發重新配置與雙重複製,同樣不適合大量迴圈串接。memcpy/lstrcpy 填充 → 最後一次性寫入。標準庫與框架類別是「黑箱」,平時假設完美直接用沒問題。但效能敏感場景下,必須理解黑箱內部機制,才能避開隱藏陷阱。下次寫大量字串拼接時,請記得:預先配置、手動維護指標、避免重複掃描與複製。