#include
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
void *memcpy(void *dest, const void *src, size_t n);
| 特性 | strcpy | strncpy | memcpy |
|---|---|---|---|
| 停止條件 | 遇到 \0 | 複製 n 字符或遇 \0 | 固定複製 n 字節 |
| 補 \0 | 自動補齊 | 若 src 長度 ≥ n,**不補 \0** | 從不處理 \0 |
| 適用場景 | 字符串整體複製 | 固定長度緩衝區、防溢出 | 二進制數據、含 \0 的內存塊 |
| 返回值 | dest | dest | dest |
| 重疊安全 | 不安全 | 不安全 | 不安全(用 memmove) |
strcpy 緩衝區溢出
char src[] = "123456789"; // 10 字節含 \0
char dest[8]; // 僅 8 字節
strcpy(dest, src); // 💥 踩內存!寫入 10 字節
後果:覆蓋相鄰變量、破壞棧幀、返回地址被改寫 → 隨機崩潰/安全漏洞。
strncpy 不補 \0 導致字符串非法
char src[] = "123456789";
char dest[8] = {0};
strncpy(dest, src, 9); // n = src 長度(含\0)
// 結果:dest = "12345678" **無結束符**!
printf("%s", dest); // 💥 讀越界直到遇到隨機 \0
修正:n = sizeof(dest) - 1,並手動補 \0:
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 保證結束
memcpy 複製字符串遇嵌入 \0 截斷
char src[] = "ab\0cd"; // 含嵌入 \0
char dest[10];
memcpy(dest, src, sizeof(src)); // 完整複製 6 字節
// dest 內容:'a','b','\0','c','d','\0' — 正確保留嵌入 \0
// 但若用 strcpy/strncpy 會在第一個 \0 停止!
結論:處理二進制數據/結構體/含 \0 內存必須用 memcpy。
| 函數 | 標準 | 特點 |
|---|---|---|
strlcpy | BSD / C23 | 保證 \0 結束,返回 src 長度 |
strcpy_s | C11 Annex K | 檢查緩衝區大小,失敗返回錯誤碼 |
memmove | C89 | 處理重疊內存區域 |
// 手寫安全版 strcpy(教學用)
char *safe_strcpy(char *dest, size_t dest_size, const char *src) {
if (dest_size == 0) return NULL;
size_t i = 0;
for (; i < dest_size - 1 && src[i]; i++)
dest[i] = src[i];
dest[i] = '\0';
return dest;
}
strcpy(或更安全的 strlcpy)strncpy + 手動補 \0,或 strlcpymemcpy(或重疊用 memmove)std::string/std::vector/std::span-Wstringop-overflow (GCC 8+):靜態分析檢測已知大小緩衝區溢出-D_FORTIFY_SOURCE=2:運行時檢查 strcpy/strncpy 等AddressSanitizer:-fsanitize=address 精準捕獲越界寫入strcpy 簡單但危險,strncpy 設計缺陷(不保證 \0),memcpy 通用但需手動管理長度。**現代 C 開發首選 strlcpy/memcpy 組合,配合編譯器硬化選項**,並在代碼規範中禁止裸用 strcpy/strncpy。理解「字符串以 \0 結束」與「內存塊按字節計數」的本質區別,是避免內存錯誤的關鍵。