strcpy/strcat/sprintf 不檢查目的緩衝區大小。strncpy/strncat 截斷時不保證以 NULL 結尾,且不回報是否發生截斷。strncat(dest, src, n) 的 n 是「最大附加字元數」而非「目的緩衝區剩餘空間」。HRESULT,SUCCEEDED(hr)/FAILED(hr) 判斷成敗。TCHAR 模型)。Ex 版本取得剩餘空間、當前指標、失敗時填充模式等進階資訊。#include <strsafe.h>
// 連結庫:strsafe.lib(Visual Studio 專案屬性 → Linker → Input → Additional Dependencies)
// 或僅包含頭文件(函式為 inline/靜態庫自動解析)
| 傳統 CRT | Strsafe 替代 | 關鍵差異 |
|---|---|---|
strcpy(dst, src) | StringCchCopy(dst, cchDst, src) | 檢查 cchDst,保證 NULL 結尾 |
strncpy(dst, src, n) | StringCchCopyN(dst, cchDst, src, cchSrc) | 明確指定來源字元數 cchSrc |
strcat(dst, src) | StringCchCat(dst, cchDst, src) | 自動計算剩餘空間 |
strncat(dst, src, n) | StringCchCatN(dst, cchDst, src, cchSrc) | 同左 |
sprintf(buf, fmt, ...) | StringCchPrintf(buf, cchBuf, fmt, ...) | 格式化並檢查長度 |
vsprintf | StringCchVPrintf | va_list 版本 |
strlen | StringCchLength(str, cchMax, &len) | 限制最大掃描長度,避免掃描未結尾字串 |
void UnsafeFunc(LPTSTR szPath, DWORD cchPath) {
TCHAR szCWD[MAX_PATH];
GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD); // 未檢查返回值
strncpy(szPath, szCWD, cchPath); // 可能不以 NULL 結尾
strncat(szPath, TEXT("\\"), cchPath); // cchPath 語意錯誤:應傳剩餘空間
strncat(szPath, TEXT("desktop.ini"), cchPath); // 同左
}
bool SaferFunc(LPTSTR szPath, DWORD cchPath) {
TCHAR szCWD[MAX_PATH];
return GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD) &&
SUCCEEDED(StringCchCopy(szPath, cchPath, szCWD)) &&
SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("\\"))) &&
SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("desktop.ini")));
}
每一步皆檢查 HRESULT,任一失敗即短路返回 false,目的緩衝區始終保持有效狀態。
HRESULT StringCchCopyEx(
LPTSTR pszDest,
size_t cchDest,
LPCTSTR pszSrc,
LPTSTR *ppszDestEnd, // 輸出:複製後字串結尾指標
size_t *pcchRemaining, // 輸出:剩餘字元數(含 NULL)
DWORD dwFlags // 行為旗標
);
STRSAFE_FILL_BEHIND_NULL:複製後以指定字元填充剩餘空間(預設 0)。STRSAFE_IGNORE_NULLS:視 NULL 為普通字元繼續複製(罕用)。STRSAFE_FILL_ON_FAILURE:失敗時以指定字元填充整個緩衝區。STRSAFE_NULL_ON_FAILURE:失敗時將緩衝區設為空字串(pszDest[0]=0)。STRSAFE_NO_TRUNCATION:視截斷為失敗(回傳 STRSAFE_E_INSUFFICIENT_BUFFER)。| 版本 | 耗時 | 相對基準 |
|---|---|---|
傳統 CRT (strcpy/strcat) | 7.3 秒 | 1.00x |
| Strsafe 標準版 | 8.3 秒 | 1.14x |
| Strsafe Ex 版(含填充 0xFE) | 11.1 秒 | 1.52x |
| Strsafe Ex 版(僅 NULL_ON_FAILURE) | 8.3 秒 | 1.14x |
結論:標準版效能損失 <15%,完全可接受;Ex 版填充操作較費時,按需啟用。
包含 strsafe.h 後,編譯器會 #undef 傳統字串函式(strcpy 等),若舊碼仍呼叫會產生編譯錯誤,強制遷移。若需保留舊函式,可在包含前定義 STRSAFE_NO_DEPRECATE。
#include <strsafe.h>(預編譯頭或強制包含)。/DSTRSAFE_NO_DEPRECATE=0 確保舊 API 被禁用。StringCchCopy/Cat/Printf 族。sizeof(buf)/sizeof(TCHAR)。HRESULT,失敗路徑記錄日誌並安全降級。Ex 版。| 平台 | 推薦庫/函式 | 特色 |
|---|---|---|
| Linux/Unix | strlcpy/strlcat (BSD) | 保證 NULL 結尾,回傳來源長度 |
| C11 Annex K | strcpy_s/strcat_s/sprintf_s | 標準化 bounds-checking 介面 |
| C++ | std::string, std::format (C++20) | RAII、型別安全、無緩衝區管理負擔 |
| Glib | g_strlcpy/g_strlcat/g_strdup_printf | GLib 生態通用 |
Strsafe.h 以極低遷移成本(僅增一行 #include)解決 C 字串處理最頑固的安全性痛點。雖非萬能藥(邏輯錯誤仍需人工防範),但統一的介面契約、編譯期強制遷移、可忽略的效能代價,使其成為 Windows 平台 C 專案的必選基礎設施。