C/C++ 標準庫 `
| 功能 | 標準函數 (不安全) | 安全替代 (C11 Annex K / MSVC) | 關鍵風險 |
|---|---|---|---|
| 複製 | strcpy(dest, src) | strcpy_s(dest, destsz, src) / strncpy_s | 緩衝區溢出 (dest 過小) |
| 複製 N 字符 | strncpy(dest, src, n) | strncpy_s(dest, destsz, src, n) | 不保證以 \0 結尾 (src≥n) |
| 追加 | strcat(dest, src) | strcat_s(dest, destsz, src) | 緩衝區溢出 |
| 追加 N 字符 | strncat(dest, src, n) | strncat_s(dest, destsz, src, n) | 緩衝區溢出 |
| 比較 | strcmp(s1, s2) | 無 (本身安全) | 無 |
| 比較 N 字符 | strncmp(s1, s2, n) | 無 | 無 |
| 分詞 | strtok(str, delim) | strtok_s(str, delim, context) | 非線程安全、修改原字符串 |
| 長度 | strlen(s) | 無 | 無 (需確保以 \0 結尾) |
// 不安全版
char *strcpy(char *dest, const char *src);
// 安全版 (C11/MSVC)
errno_t strcpy_s(char *dest, rsize_t destsz, const char *src);
// 關鍵點:
// - dest 必須有足夠空間容納 src + \0
// - strcpy_s 會檢查 destsz,失敗返回錯誤碼並將 dest[0] = \0
// - 返回 0 表示成功
// 不安全版
char *strncpy(char *dest, const char *src, size_t n);
// 安全版
errno_t strncpy_s(char *dest, rsize_t destsz, const char *src, size_t n);
// 陷阱:若 src 長度 ≥ n,dest **不會** 自動添加 \0 結尾!
// 安全模式:手動確保結尾
strncpy_s(buf, sizeof(buf), src, sizeof(buf)-1);
buf[sizeof(buf)-1] = '\0'; // 強制結尾
// 不安全版
char *strcat(char *dest, const char *src);
// 安全版
errno_t strcat_s(char *dest, rsize_t destsz, const char *src);
// 必須確保 dest 有足夠剩餘空間
// 現代寫法:snprintf 或 std::string
int strcmp(const char *s1, const char *s2); // 字典序比較
int strncmp(const char *s1, const char *s2, size_t n); // 比較前 n 字符
// 返回值:
// < 0 : s1 < s2
// = 0 : s1 == s2
// > 0 : s1 > s2
// 注意:區分大小寫,按 ASCII/字節值比較
// 不區分大小寫:_stricmp / strcasecmp (POSIX)
// 不安全版:內部靜態指針保存狀態,非線程安全,修改原字符串
char *strtok(char *str, const char *delim);
// 安全版:顯式傳遞上下文指針
char *strtok_s(char *str, const char *delim, char **context);
// 用法模式:
char *ctx = NULL;
char *token = strtok_s(str, " ,.;", &ctx); // 首次傳 str
while (token) {
// 處理 token
token = strtok_s(NULL, " ,.;", &ctx); // 後續傳 NULL
}
// 現代替代:std::stringstream, std::regex, boost::split, C++20 std::split
size_t strlen(const char *s);
// 返回 \0 前的字符數 (不含 \0)
// 前提:s 必須以 \0 結尾,否則越界讀取
// 無安全版,因為只讀不寫
#include
#include
#include
int main() {
// 1. strcpy / strncpy
char src[] = "Hello, World!";
char dest1[50];
char dest2[10]; // 故意較小
strcpy(dest1, src); // 安全:dest1 足夠大
printf("strcpy: %s\n", dest1);
// strncpy 安全用法
strncpy(dest2, src, sizeof(dest2)-1);
dest2[sizeof(dest2)-1] = '\0'; // 強制結尾
printf("strncpy (truncated): %s\n", dest2);
// 2. strcat / strncat
char base[50] = "Prefix: ";
strcat(base, src);
printf("strcat: %s\n", base);
char base2[20] = "Prefix: ";
strncat(base2, src, sizeof(base2) - strlen(base2) - 1);
printf("strncat: %s\n", base2);
// 3. strcmp / strncmp
printf("strcmp('abc', 'abd'): %d\n", strcmp("abc", "abd")); // -1
printf("strncmp('abc', 'abd', 2): %d\n", strncmp("abc", "abd", 2)); // 0 (前2字符相同)
// 4. strtok_s 分詞
char sentence[] = "This is a sentence with 7 tokens";
char *ctx = NULL;
char *token = strtok_s(sentence, " ", &ctx);
printf("Tokens:\n");
while (token) {
printf(" [%s]\n", token);
token = strtok_s(NULL, " ", &ctx);
}
// 注意:sentence 已被修改 (分隔符替換為 \0)
// 5. strlen
printf("strlen(src): %zu\n", strlen(src));
return 0;
}
#include
#include
#include // C++17
#include
// 複製/賦值/追加/比較/長度 全部安全、直觀、無緩衝區風險
std::string s1 = "Hello";
std::string s2 = s1 + ", World!"; // 追加
if (s1 == "Hello") {} // 比較
size_t len = s1.size(); // 長度
// 分詞 (C++20 std::split 或範圍庫)
std::string_view sv = "a,b,c";
for (auto token : sv | std::views::split(',')) {
std::string_view part(token.begin(), token.end());
// 處理 part
}
// 舊 API 互操作:c_str() (只讀), data() (C++17 保證 \0 結尾)
// 傳給 C API: legacy_func(s1.c_str());
// 接收 C API 返回: std::string result(c_api_return_ptr); // 假設以 \0 結尾
C 標準庫字符串函數 (strcpy, strcat, strcmp, strtok, strlen) 是系統編程基石,但**緩衝區溢出、非線程安全、修改原串**等缺陷導致無數安全漏洞。**現代 C/C++ 開發鐵律**:
_s 後綴 / `strncpy_s` 等) 並嚴格檢查返回碼。掌握 C 字符串函數語義與陷阱,是讀懂舊代碼、編寫高性能解析器、進行安全審計的基本功;但日常開發請堅定選擇 `std::string`。