原始碼存在**參數順序顛倒**、**返回值錯誤**、**無緩衝區檢查**三大缺陷。本文提供標準 `strcat` 實現、安全版 `strncat`、`strlcat` 最佳實踐。
// 原始碼:
char *my_strcat(char *dest, char *src) { // src 應為 const
char *ret = src; // 錯誤:應返回 dest
while (*src) src++; // 找 src 末尾?應找 dest 末尾
while (*src++ = *dest++) ; // 參數順序反了!應 dest 接收 src
return ret; // 返回錯誤
}
#includechar *my_strcat(char *dest, const char *src) { assert(dest && src); char *ret = dest; // 1. 找到 dest 結束符 while (*dest) dest++; // 2. 拼接 src (含 \0) while ((*dest++ = *src++) != '\0') ; return ret; }
char *my_strncat(char *dest, const char *src, size_t n) {
assert(dest && src);
char *ret = dest;
while (*dest) dest++;
while (n-- && *src) {
*dest++ = *src++;
}
*dest = '\0'; // 確保結束符
return ret;
}
// 非標準但廣泛可用 (BSD, Linux, macOS)
size_t strlcat(char *dest, const char *src, size_t dest_size) {
size_t dlen = strlen(dest);
if (dlen >= dest_size) return dlen + strlen(src); // dest 已滿
size_t n = dest_size - dlen - 1; // 留 1 位給 \0
char *d = dest + dlen;
const char *s = src;
while (n-- && *s) *d++ = *s++;
*d = '\0';
return dlen + (s - src); // 返回嘗試建立的總長度
}
int main() {
char buf[10] = "hello"; // 容量 10,已用 6 (含\0),剩 4
char *src = "world!"; // 長度 6
// 危險:strcat 無長度檢查
strcat(buf, src); // 寫入 6 字元 + \0 = 7 字元,溢出 3 字元!
// 結果:緩衝區溢出,未定義行為 (可能崩潰、靜默損壞、安全漏洞)
// 安全:strncat
char buf2[10] = "hello";
strncat(buf2, src, sizeof(buf2) - strlen(buf2) - 1); // 最多複製 3 字元
// 結果:"hello wor"
// 最佳:strlcat
char buf3[10] = "hello";
strlcat(buf3, src, sizeof(buf3)); // 自動處理邊界
// 結果:"hello wor"
return 0;
}
| 錯誤 | 後果 | 修正 |
|---|---|---|
| 參數順序反 (src, dest) | 邏輯完全錯誤 | 標準為 (dest, src) |
| 返回 src 而非 dest | 鏈式調用失效 | 保存並返回 dest |
| src 非 const | 語義錯誤,阻礙優化 | const char *src |
| 無緩衝區檢查 | 溢出漏洞 | 用 strncat/strlcat 或預先檢查 |
| 忘記確保 \0 | 字串未正確終止 | strncat 手動補 \0,strlcat 自動 |
`strcat` = **危險、無長度檢查、棄用**。現代代碼**強制使用 `strlcat` 或 `strncat` + 精確長度計算**。原始碼參數順序反、返回值錯、無 `const`,屬於基礎語義錯誤。面試必會:`strcat`/`strncat`/`strlcat` 三者差異、緩衝區溢出風險與防範、返回值語義差異 (指標 vs 長度)。