字符串

C 语言字符串处理完全指南

C 语言的字符串处理是每个 C 开发者必须掌握的基本功。不同于高级语言,C 字符串本质上是字符数组,需要手动管理内存。本文详解常用字符串函数的原理、常见陷阱与安全用法。

一、字符串基础与存储模型

C 语言中没有专门的"字符串类型",字符串以字符数组形式存储,以空字符 \0 结尾:

char s[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
char s2[] = "hello";  /* 简化写法,自动添加 \0 */

字符串长度不包括结尾的 \0strlen("hello") 返回 5,但存储需要 6 字节。

二、常用字符串函数详解

1. 复制函数

/* strcpy - 不安全,有缓冲区溢出风险 */
char dest[20];
strcpy(dest, src);  /* 如果 src > 20 会崩溃 */

/* strncpy - 指定长度复制,但可能不添加 \0 */
strncpy(dest, src, 19);
dest[19] = '\0';  /* 必须手动添加结尾符 */

/* snprintf - 安全版本,推荐使用 */
snprintf(dest, sizeof(dest), "%s", src);

2. 连接函数

/* strcat - 连接两个字符串 */
char buf[100] = "Hello";
strcat(buf, " World");  /* buf 变为 "Hello World" */

/* strncat - 安全版本,自动确保 \0 */
strncat(buf, src, 99 - strlen(buf));

3. 比较函数

/* strcmp - 按字典序比较,返回值:<0/0/>0 */
if (strcmp(s1, s2) == 0) {
    printf("相同\n");
}

/* strncmp - 比较前 n 个字符 */
strncmp(s1, s2, 3);  /* 只比较前 3 个字符 */

/* strcasecmp - 不区分大小写比较(Linux 特有) */
strcasecmp("ABC", "abc");  /* 返回 0 */

4. 搜索函数

/* strchr - 查找字符首次出现位置 */
char *p = strchr("hello", 'l');  /* 指向第二个 'l' */

/* strrchr - 查找字符最后一次出现 */
char *p2 = strrchr("hello", 'l');  /* 指向第三个 'l' */

/* strstr - 子串查找 */
char *p3 = strstr("hello world", "world");

三、字符串转换函数

/* 字符串转数值 */
int i = atoi("123");        /* 不推荐,无错误检测 */
long l = strtol(s, &end, 10);  /* 推荐,可检测错误 */
double d = strtod(s, &end);

/* 数值转字符串 */
char buf[32];
snprintf(buf, sizeof(buf), "%d", 123);

四、常见安全问题

缓冲区溢出是 C 语言中最常见的安全漏洞之一。攻击者通过溢出覆盖返回地址,可以执行任意代码。
  • 始终使用有界限的函数:用 snprintf 替代 sprintf,用 strncpy 替代 strcpy
  • 检查返回值:函数返回 NULL 时要妥善处理
  • 预留空间:确保目标缓冲区足够大,考虑最坏情况
  • 使用静态分析工具:如 Coverity、cppcheck 可检测潜在问题

五、字符串操作技巧

/* 去除字符串首尾空白 */
char *trim(char *s) {
    while (isspace(*s)) s++;  /* 跳过开头空白 */
    char *end = s + strlen(s) - 1;
    while (end > s && isspace(*end)) end--;
    *(end + 1) = '\0';
    return s;
}

/* 字符串分割 */
char s[] = "a,b,c,d";
char *token;
char *saveptr;
for (token = strtok_r(s, ",", &saveptr); token;
     token = strtok_r(NULL, ",", &saveptr)) {
    printf("%s\n", token);  /* 线程安全版本 */
}

六、总结

  • 牢记 \0 是字符串的一部分,计算长度时要考虑
  • 优先使用有边界检查的函数(snprintf、strncpy、strncat)
  • 处理用户输入时务必验证长度与内容
  • 使用 strtolstrtod 等带错误检测的转换函数
进阶阅读:《C 陷阱与缺陷》《Expert C Programming》中有更多字符串处理的最佳实践。