首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言標準庫字符串函數核心實現:strcat/strcmp/strcpy/strncpy/strrchr 源碼級解析

     編輯:關於C語言

前言

標準庫字符串函數是 C 程序員每天打交道的基石。本文精選 strcat、strcmp、strcpy、strncpy、strrchr 五大核心函數,**給出經典實現源碼 (兼容主流 libc 風格)**,逐行解析指針算術、斷言防禦、返回值設計、邊界處理,幫助讀者從「會用」進階到「懂實現、能手寫、知優化」。

一、strcat:字符串拼接 (追加)

1.1 標準簽名與契約


char *strcat(char *restrict dest, const char *restrict src);
// 將 src 拼接到 dest 末尾 (覆蓋 dest 的 '\0'),返回 dest
// 調用者保證 dest 空間足夠容納結果

1.2 經典實現 (glibc 風格)


char *strcat(char *dest, const char *src) {
    char *ret = dest;                 // 保存原始 dest,用於返回 (鏈式調用)
    
    // 1. 定位 dest 末尾 '\0'
    while (*dest) dest++;             // 等價 while (*dest != '\0') dest++;
    // 注意:不能寫 while (*dest++),會多遞增一次導致指向 '\0' 後
    
    // 2. 拷貝 src 直到 '\0' (含 '\0')
    while ((*dest++ = *src++) != '\0')
        ; // 空循環體,賦值結果即為判斷條件
    
    return ret;
}

1.3 關鍵點

  • 返回原指針:支持 strcat(strcat(buf, s1), s2) 鏈式調用。
  • 無長度檢查:調用者責任保證 dest 足夠大,否則緩衝區溢出。**生產代碼首選 strncat 或 strlcat**。
  • 指針遞增順序:先判斷 *dest 再 dest++,確保停在 \0 上;拷貝時 *dest++ = *src++ 將賦值與判斷融合。

二、strcmp:字符串比較 (字典序)

2.1 標準簽名


int strcmp(const char *s1, const char *s2);
// 返回 <0, =0, >0 對應 s1 <, =, > s2 (按 unsigned char 比較)

2.2 經典實現 (BSD 風格,無斷言版)


int strcmp(const char *s1, const char *s2) {
    // 轉為 unsigned char 避免負值字符比較異常 (符號擴展)
    const unsigned char *p1 = (const unsigned char *)s1;
    const unsigned char *p2 = (const unsigned char *)s2;
    
    while (*p1 && *p1 == *p2) { // 相同且未結束
        p1++;
        p2++;
    }
    // 差異處或結束處:返回差值 (自動轉為 int)
    return *p1 - *p2;
}

2.3 關鍵點

  • unsigned char 比較:標準要求按無符號字符比較,避免 char 有符號時 0xFF > 0x01 判斷錯誤。
  • 早退出:首個差異字符即決定結果,無需遍歷全串。
  • 返回值語義:非標準化的 -1/0/1,而是實際差值 (如 'a' - 'b' = -1),但符號正確即可。

三、strcpy:字符串拷貝

3.1 標準簽名


char *strcpy(char *restrict dest, const char *restrict src);
// 拷貝 src (含 '\0') 到 dest,返回 dest

3.2 極簡實現 (K&R 風格)


char *strcpy(char *dest, const char *src) {
    char *ret = dest;
    while ((*dest++ = *src++) != '\0')
        ;
    return ret;
}

3.3 安全增強版 (帶斷言)


char *strcpy(char *dest, const char *src) {
    assert(dest != NULL && src != NULL); // 防禦性編程
    char *ret = dest;
    while ((*dest++ = *src++) != '\0')
        ;
    return ret;
}

3.4 關鍵點

  • 含 '\0' 拷貝:循環條件 != '\0' 確保結束符也被拷貝。
  • 無重疊保證:restrict 關鍵字 (C99) 告訴編譯器 dest 與 src 不重疊,允許向量化優化。重疊拷貝需用 memmove。

四、strncpy:定長字符串拷貝 (固定寬度字段處理)

4.1 標準簽名


char *strncpy(char *dest, const char *src, size_t n);
// 最多拷貝 n 字符。src 短於 n → dest 補 '\0' 至 n。src 長於 n → 截斷,**不自動添加 '\0'**。

4.2 經典實現


char *strncpy(char *dest, const char *src, size_t n) {
    char *ret = dest;
    while (n--) {
        if ((*dest = *src) != '\0') src++; // src 未結束才推進 src
        dest++;
    }
    return ret;
}

4.3 關鍵陷阱與最佳實踐

  • ❌ 不保證以 '\0' 結尾:若 strlen(src) >= n,dest 無結束符,後續 strlen/printf 會越界讀取。
  • ✅ **手動添加結束符**:dest[n-1] = '\0'; (若 n>0)。
  • ✅ **或用 strlcpy (BSD) / strncpy_s (C11 Annex K)**:保證以 '\0' 結尾,返回源長度。

五、strrchr:反向查找字符最後出現位置

5.1 標準簽名


char *strrchr(const char *s, int c);
// 查找 c (轉為 unsigned char) 在 s 中**最後一次**出現的地址,含結束符。
// 找到返回指針,否則 NULL。

5.2 經典實現 (glibc 風格,利用 strchr 循環)


char *strrchr(const char *s, int c) {
    c = (unsigned char)c;
    if (c == '\0') return strchr(s, '\0'); // 查結束符直接用 strchr
    
    const char *found = NULL;
    const char *p;
    while ((p = strchr(s, c)) != NULL) { // 反覆向前查找
        found = p;
        s = p + 1; // 從下一字符繼續找
    }
    return (char *)found;
}

5.3 關鍵點

  • 利用 strchr 復用:strchr 通常高度優化 (SIMD/汇編),反覆調用比手寫反向循環更快。
  • 查 '\0' 特判:直接調用 strchr,避免循環邏輯錯誤。
  • 返回類型轉換:const char* → char* (標準規定),調用者需知曉原字符串可修改性。

六、總結:字符串函數實現的「黃金法則」

  1. 指針算術優於下標:while (*p++) 比 for(i=0; s[i]; i++) 更快、更符合 C 語言本質。
  2. 賦值即判斷:while ((*d++ = *s++) != '\0') 融合拷貝與終止檢查,零分支。
  3. 無符號字符比較:所有字符比較轉 unsigned char,符合標準、避免符號擴展 Bug。
  4. 返回原始指針:支持鏈式調用,零開銷。
  5. 防禦性斷言:assert(ptr != NULL) 捕獲空指針傳入,零發布版開銷 (NDEBUG)。
  6. 調用者負責邊界:標準庫函數不檢查緩衝區大小,**安全版本用 strncat/strlcpy/snprintf 系列**。

七、手寫面試級 strcpy 模板


char *my_strcpy(char *dest, const char *src) {
    if (!dest || !src) return NULL; // 或 assert
    char *ret = dest;
    // 可選:檢查重疊 (dest 在 src 區間內) → 用 memmove 邏輯
    while ((*dest++ = *src++) != '\0');
    return ret;
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved