【C语法硬核20讲】10 字符串处理:strcpy替代

目标:在 不破坏性能 的前提下,写出边界安全、可维护的字符串代码;给出 strcpy/strcat 的替代方案、容量与长度的双指标思维、以及 UTF-8 等多字节编码的注意事项。


1)为什么 strcpy/strcat 不安全?

  • 不感知容量:一旦源比目标长就越界。
  • 不自动加边界:错误难以暴露,常在上线后“随机崩”。
  • 多字节编码下更难排查。

2)容量与长度的双指标

  • 容量(capacity):目标缓冲区能容纳的最大字节数 cap。
  • 长度(length):当前字符串有效字节数,不含终止符 '\0'。
    规则:任何写入都必须保证 len < cap 且写完 buf[last]='\0'

3)替代方案速查

  • 组合拳(标准可移植):snprintf + strnlen + memcpy/memmove。
  • 可选增强(平台相关):strlcpy/strlcat(BSD/部分平台)、asprintf(GNU 扩展)。

安全拷贝模板(标准库即可)

// 把 src 拷贝进 dst,确保以 '\0' 结尾且不越界
void safe_copy(char *dst, size_t cap, const char *src){
    if (cap == 0) return;
    size_t n = strnlen(src, cap-1);
    memcpy(dst, src, n);
    dst[n] = '\0';
}

安全拼接模板

size_t safe_cat(char *dst, size_t cap, const char *suffix){
    size_t dlen = strnlen(dst, cap);
    if (dlen == cap) return dlen; // 已被截断
    size_t room = cap - dlen - 1;
    size_t n = strnlen(suffix, room);
    memcpy(dst + dlen, suffix, n);
    dst[dlen + n] = '\0';
    return dlen + n;
}

格式化优先用 snprintf

snprintf(buf, sizeof buf, "%s-%d", tag, id); // 自动截断 + 结尾 '\0'

4)重叠区域:memmove 胜于 memcpy

// 原地删除前缀 prefix(若存在)
void drop_prefix(char *s, const char *prefix){
    size_t n = strlen(prefix);
    if (strncmp(s, prefix, n)==0) memmove(s, s+n, strlen(s+n)+1);
}

memcpy 对重叠未定义;memmove 处理重叠安全。


5)动态拼接:可增长“字符串构建器”

struct sbuf { char *p; size_t len, cap; };

int sbuf_append(struct sbuf *b, const char *s){
    size_t add = strlen(s);
    if (b->len + add + 1 > b->cap) {
        size_t need = b->len + add + 1;
        size_t newcap = b->cap? b->cap*2 : 32;
        if (newcap < need) newcap = need;
        char *tmp = realloc(b->p, newcap);
        if (!tmp) return -1;
        b->p = tmp; b->cap = newcap;
    }
    memcpy(b->p + b->len, s, add+1);
    b->len += add;
    return 0;
}

6)编码注意:UTF-8 ≠ 宽字符

  • UTF-8 的“长度”是字节数不是字符数;一个“字”可能占 3 字节。
  • 需要“字符级”裁剪/截断时,必须沿着 码点边界 处理,或转成 宽字符(wchar_t)/库处理。
  • 混合中文/表情符时,避免按字节随意 substr,容易“半个字”变乱码。

7)输入读取:拒绝 gets,谨慎 scanf("%s")

  • gets 已被移除。
  • scanf("%s", buf) 无边界 → 用 "%15s" 这种限制宽度,或用 fgets。
  • 文本行读取常用:fgets(buf, sizeof buf, fp),随后去掉末尾 '\n'。

8)自检清单

  • 始终带容量参数
  • 写完保证 '\0' 终止
  • 重叠用 memmove
  • 动态构建器按倍数扩容
  • UTF-8 操作遵循码点边界
  • 输入函数限制宽度或用 fgets

9)迷你练习

1)把 strcpy(dst, src) 改写成容量安全的版本(见 §3)。
2)写 trim_right(char *s):去掉行尾 \r\n\t 空格,不越界。
3)写 join3(buf,cap,a,b,c):把三个字符串按顺序拼接入 buf。

原文链接:,转发请注明来源!