字符串处理函数:strlen、strcpy、strcat、strcmp 的用法与手写实现
字符串处理,是 C 语言里绕不开的坎儿。说实话,我当年刚入行时,觉得这些函数太简单了,不就是数数长度、复制一下嘛。直到有一次线上服务因为 strcpy 缓冲区溢出直接崩溃,我才真正重视起来。嗯,今天咱们就把这四个函数彻底讲透——怎么用、怎么手写、怎么避坑。
1. strlen:别被长度骗了
strlen 返回的是字符串的长度,但不包括结尾的 '\0'。这个细节,我见过太多新手搞混。
#include <string.h>
char str[] = "Hello";
size_t len = strlen(str); // len = 5,不是 6
为什么会这样?因为 strlen 从起始地址开始数,一直数到第一个 '\0' 为止。它不关心数组实际有多大,只关心 '\0' 在哪里。
'\0' 结尾,strlen 会一直往后数,直到遇到内存中的某个 '\0'。这可能导致返回一个巨大的值,甚至引发段错误。
手写一个 strlen 其实很简单:
size_t my_strlen(const char *s) {
const char *p = s;
while (*p != '\0') {
p++;
}
return (size_t)(p - s);
}
我个人习惯用指针减法来算长度,这样代码更简洁。你想想看,如果传进来的是空指针怎么办?嗯,标准库没做这个检查,所以调用前你自己得保证 s 不为 NULL。
2. strcpy:复制有风险,拷贝需谨慎
strcpy 把源字符串复制到目标缓冲区,包括结尾的 '\0'。但它不做边界检查——这是最大的坑。
char dest[10];
char *src = "This is a very long string";
strcpy(dest, src); // 缓冲区溢出!dest 只有 10 字节
我曾经在一个嵌入式项目里遇到过这个问题。当时有个日志模块用 strcpy 拼接路径,结果用户输入了一个超长文件名,直接把栈上的返回地址给覆盖了。嗯,从那以后,我团队里就立了个规矩:能用 strncpy 就别用 strcpy。
手写实现:
char *my_strcpy(char *dest, const char *src) {
char *ret = dest;
while ((*dest++ = *src++) != '\0') {
// 空循环体,赋值操作已经完成
}
return ret;
}
'\0'。这样连结尾的 '\0' 也一起复制了,代码非常紧凑。
3. strcat:拼接字符串,小心越界
strcat 把源字符串追加到目标字符串的末尾。它会先找到目标字符串的 '\0',然后从那里开始复制。
char buf[20] = "Hello";
strcat(buf, " World"); // buf 变成 "Hello World"
但问题来了:如果 buf 剩余空间不够呢?strcat 照样会写出去。我建议你每次用 strcat 前,先算算还剩多少空间。
手写实现:
char *my_strcat(char *dest, const char *src) {
char *ret = dest;
// 先找到 dest 的末尾
while (*dest != '\0') {
dest++;
}
// 从末尾开始复制 src
while ((*dest++ = *src++) != '\0') {
// 空循环体
}
return ret;
}
说白了,strcat 就是先 strlen 定位,再 strcpy 复制。两个步骤合在一起。
4. strcmp:比较的不是长度,是字典序
strcmp 逐字节比较两个字符串的 ASCII 码值。它返回:
| 返回值 | 含义 |
|---|---|
| < 0 | 第一个字符串小于第二个 |
| 0 | 两个字符串相等 |
| > 0 | 第一个字符串大于第二个 |
if (strcmp("apple", "banana") < 0) {
printf("apple 排在 banana 前面\n");
}
手写实现:
int my_strcmp(const char *s1, const char *s2) {
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) {
s1++;
s2++;
}
return (unsigned char)(*s1) - (unsigned char)(*s2);
}
unsigned char?因为 char 在某些平台上是有符号的,如果字符的 ASCII 码大于 127,直接相减可能得到负数。转成无符号能保证比较结果正确。
5. 常见安全问题与避坑指南
这四个函数,除了 strlen 是只读的,其他三个都可能造成缓冲区溢出。我总结了几条经验:
- 永远不要相信输入长度。 用户输入、网络数据、文件内容,都可能超出你的预期。
- 优先使用带长度限制的版本:
strncpy、strncat、strncmp。虽然它们也有自己的坑(比如strncpy不自动加'\0'),但至少能控制写入量。 - 手动加
'\0'保平安。 用strncpy后,记得在目标数组末尾手动置'\0'。 - 检查返回值。 有些安全版本的函数会返回错误码,别忽略它们。
strcat 拼接 HTTP 头部。结果某个请求的头部特别长,直接把后面的内存写坏了。从那以后,我所有字符串操作都改用 snprintf 或者自己写带长度检查的版本。
6. 知识体系图
下面这张图帮你理清这四个函数的关系和核心要点:
snprintf 替代 strcpy 和 strcat。它既能控制输出长度,又能自动加 '\0',省心很多。比如 snprintf(dest, sizeof(dest), "%s%s", prefix, suffix); 比 strcat 安全得多。
好了,这四个函数的核心用法和手写实现就讲到这里。记住:字符串操作无小事,一个 '\0' 的缺失就可能让整个程序崩溃。写代码时多问自己一句:这个缓冲区够大吗?输入长度可控吗?养成这个习惯,能帮你省下无数调试时间。
公众号:蓝海资料掘金营,微信deep3321