`strcpy` 是最基礎也最危險的字串函數:**無長度檢查、極易緩衝區溢出**。本文提供標準實現、C11 `strcpy_s`、BSD `strlcpy` 安全替代方案,並修正原始碼註釋中的邏輯混淆。
#includechar *my_strcpy(char *dest, const char *src) { assert(dest && src); char *ret = dest; // 核心:複製含 \0,賦值表達式結果為賦予的值 while ((*dest++ = *src++) != '\0') ; return ret; }
// 原始碼註釋:
while ((*dest++ = *src++)) ; // 正確:含 \0 複製
/*while (*src) { // 替代寫法:漏複製 \0
*dest = *src;
dest++; src++;
}*/
// 原串為 \0 時跳出,並未拷貝 \0, 要賦值 *dest = '\0'
// 原註釋邏輯混淆:第一種寫法已含 \0,第二種才需手動補 \0
| 函數 | 標準 | 簽名 | 行為 | 返回值 |
|---|---|---|---|---|
| strcpy | C89 | (dest, src) | 無檢查,高危 | dest |
| strncpy | C89 | (dest, src, n) | 固定寫 n 字元,不保證 \0 | dest |
| strcpy_s | C11 (Annex K) | (dest, destsz, src) | 檢查邊界,失敗返回錯誤碼 | errno_t (0成功) |
| strlcpy | BSD/常見擴展 | (dest, src, destsz) | 保證 \0,返回 src 長度 | size_t (嘗試長度) |
#define __STDC_WANT_LIB_EXT1__ 1 #include#include errno_t my_strcpy_s(char *dest, rsize_t destsz, const char *src) { if (dest == NULL || src == NULL || destsz == 0 || destsz > RSIZE_MAX) { if (dest && destsz) *dest = '\0'; return EINVAL; } rsize_t i = 0; while (i < destsz && src[i]) { dest[i] = src[i]; i++; } if (i == destsz) { // src 過長,未遇 \0 dest[0] = '\0'; return ERANGE; } dest[i] = '\0'; return 0; }
size_t my_strlcpy(char *dest, const char *src, size_t destsz) {
const char *s = src;
size_t n = destsz;
if (n) {
while (--n && *s) *dest++ = *s++;
*dest = '\0';
}
// 計算 src 剩餘長度 (含已複製部分)
while (*s++) ;
return (s - src - 1);
}
int main() {
char buf[10];
const char *src = "hello world!"; // 12 字元 + \0 = 13
// 危險:strcpy 溢出
// strcpy(buf, src); // 寫入 13 字元到 10 位元組緩衝區 → UB
// 不安全:strncpy 不保證 \0
// strncpy(buf, src, sizeof(buf)); // buf 可能無 \0!
// 安全:strlcpy (推薦)
size_t len = strlcpy(buf, src, sizeof(buf));
printf("strlcpy: buf=\"%s\", 返回長度=%zu (截斷=%s)\n",
buf, len, len >= sizeof(buf) ? "是" : "否");
// 輸出: buf="hello wo", 返回長度=12 (截斷=是)
// 安全:strcpy_s (C11)
#ifdef __STDC_LIB_EXT1__
errno_t err = strcpy_s(buf, sizeof(buf), src);
printf("strcpy_s: buf=\"%s\", 錯誤碼=%d\n", buf, err);
#endif
return 0;
}
`strcpy` = **棄用、高危**。`strncpy` = **陷阱、不保證 \0、固定寫入**。**現代代碼強制使用 `strlcpy` (返回長度、保證 \0) 或 `strcpy_s` (C11、錯誤碼)**。原始碼實現正確但註釋邏輯混淆:第一種 `while(*d++=*s++)` 已含 `\0`,第二種 `while(*s)` 才需手動補 `\0`。面試必會:四者差異、緩衝區溢出風險、返回值語義、標準歸屬。