C 語言無內建字符串類型,字符串拼接本質是將源字符串內容複製到目標字符串尾部,並確保以 '\0' 結尾。本文通過一個經典的指鉤實現版本,深入剖析其邏輯、隱患,並對比標準庫 strcat/strncat 及安全版本的最佳實踐。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h> // 實際開發建議包含
// 將字符串 t 拼接到字符串 s 末尾
void link(char *s, char *t) {
// 1. 定位 s 的結尾 '\0'
while (*s != '\0') {
s++;
}
// 2. 複製 t 到 s 末尾,包含結尾 '\0'
while (*t != '\0') {
*s++ = *t++;
}
*s = '\0'; // 3. 關鍵:手動添加結尾符
}
int main() {
char p[128] = "123"; // 目標緩衝區必須足夠大!
char *q = "456"; // 源字符串 (常量區,只讀)
link(p, q);
printf("連接後的 p: %s\n", p); // 輸出: 123456
// system("pause"); // 非跨平台,建議用 getchar()
getchar();
return 0;
}
代碼中 char p[128] 固定大小,若 `strlen(p) + strlen(q) >= 128`,將導致**緩衝區溢出**,覆蓋棧上相鄰內存,引發崩潰或安全漏洞。此函數無長度檢查,屬高危實現。
| 函數 | 簽名 | 特點 | 安全性 |
|---|---|---|---|
strcat | char *strcat(char *dest, const char *src) | 直接拼接,無長度檢查 | ❌ 高危 |
strncat | char *strncat(char *dest, const char *src, size_t n) | 最多追加 n 字符,自動補 `\0` | ⚠️ 需正確計算 n |
strlcat (BSD) | size_t strlcat(char *dst, const char *src, size_t size) | 以總緩衝區大小為限,返回嘗試長度 | ✅ 推薦 (非標準) |
strcat_s (C11 Annex K) | errno_t strcat_s(char *dest, rsize_t destsz, const char *src) | 檢查緩衝區邊界,失敗返回錯誤碼 | ✅ 最佳 (需編譯器支持) |
// 移植性較好的安全拼接封裝
#include <string.h>
#include <stdio.h>
int safe_strcat(char *dest, size_t dest_size, const char *src) {
size_t dest_len = strlen(dest);
if (dest_len >= dest_size) return -1; // 已滿或異常
size_t remain = dest_size - dest_len - 1; // 留 1 位給 '\0'
if (remain == 0) return -1;
strncat(dest, src, remain);
return 0;
}
int main() {
char buf[32] = "Hello";
safe_strcat(buf, sizeof(buf), " World!");
printf("%s\n", buf); // 安全輸出
return 0;
}
char *dynamic_concat(const char *s1, const char *s2) {
size_t len1 = strlen(s1), len2 = strlen(s2);
char *result = malloc(len1 + len2 + 1);
if (!result) return NULL;
memcpy(result, s1, len1);
memcpy(result + len1, s2, len2 + 1); // 包含 '\0'
return result; // 調用者負責 free()
}
strncat 並精確計算剩餘空間,或引入 strlcat/strcat_s。struct { char *data; size_t len, cap; })實現擴容策略,避免反覆拷貝。