1. 原理与细节讲解
C语言中的字符串是以'\0'
(null字符,ASCII为0)结尾的字符数组。操作字符串时,很多标准库函数(如strcpy
、strlen
、strcat
等)依赖这个结尾标志来判断字符串的边界。
常见问题在于:
- 没有正确以
'\0'
结尾,导致函数继续读取/写入后续未初始化或属于其他变量的内存。 - 拷贝或拼接时未考虑目标数组的大小,容易导致缓冲区溢出(Buffer Overflow)。
缓冲区溢出是C语言里最严重的漏洞之一,能导致崩溃、数据泄露,甚至攻击者执行任意代码(如著名的“栈溢出攻击”)。
2. 典型陷阱/缺陷
-
字符串未以
'\0'
结尾char buf[5] = {'h', 'e', 'l', 'l', 'o'}; // 没有'\0' printf("%s\n", buf); // 未定义行为,可能输出乱码或崩溃
-
strcpy
/strcat
不检查长度char buf[8]; strcpy(buf, "supercalifragilistic"); // 溢出
成因:
C的字符串管理完全依赖开发者手动保证结尾和空间,标准库函数本身不会自动帮你判断目标空间是否够用。
3. 规避方法与设计建议
- 声明字符串数组时多留一个空间给
'\0'
。 - 初始化数组,如
char buf[10] = {0};
。 - 优先使用安全函数如
strncpy
/strncat
/snprintf
,手动传递长度,防止溢出。 - 操作后显式设置结尾,如
buf[sizeof(buf)-1] = '\0';
。 - 对于用户输入,始终做长度检查。
- 使用标准库的安全接口(如C11的
strcpy_s
等)或第三方安全库。
4. 代码示例
错误代码
#include <stdio.h>
#include <string.h>
void bad_example() {
char name[8];
strcpy(name, "0123456789abcdef"); // 超长拷贝
printf("name: %s\n", name); // 未定义行为
}
正确代码
#include <stdio.h>
#include <string.h>
void good_example() {
char name[8];
strncpy(name, "0123456789abcdef", sizeof(name) - 1);
name[sizeof(name) - 1] = '\0'; // 确保结尾
printf("name: %s\n", name); // 安全输出
}
对比分析
- 错误代码中
strcpy
完全不考虑目标空间,数据溢出,破坏栈上其他变量。 - 正确代码用
strncpy
限制拷贝长度,并手动设置\0
保证安全。
5. 底层原理
- C字符串遍历依赖内存中第一个
\0
作为终止;没有\0
,API会继续读下去,可能读到非法或敏感数据。 - 溢出时,紧邻的变量、返回地址都可能被覆盖,造成严重后果。
- 栈溢出攻击利用了溢出覆盖返回地址或函数指针,劫持程序流程。
6. 图示
7. 总结与建议
字符串操作是C语言安全事故的重灾区。永远不要假设C会自动为你加上\0
或检查数组边界。
在声明和操作字符串时,要主动考虑结尾和空间,优先使用安全函数,手动设置结尾。代码审查时,字符串相关的越界和结尾问题必须作为重点。
实际开发建议:
“每次写字符串操作,都要问自己:‘内存够吗?结尾安全了吗?’”
公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top