字符串的暗礁:字面量不可修改、缺少空字符终止、缓冲区溢出
大家好,我是你们的老朋友。今天咱们聊聊字符串——C语言里最常用、也最容易翻车的地方。
字符串这东西,看着简单,其实暗礁密布。我做了十几年嵌入式开发,见过太多因为字符串出问题的bug。有的让设备死机,有的让数据错乱,有的甚至让整个系统崩溃。说白了,C语言的字符串就是一块充满陷阱的沼泽地。
嗯,咱们今天就把这些暗礁一个一个挖出来,看看它们到底藏在哪儿。
暗礁一:字符串字面量不可修改
先看一个最常见的坑。
char *p = "hello";
p[0] = 'H'; // 运行时崩溃!
为什么会这样?
因为 "hello" 是字符串字面量,它存储在只读数据段。你试图修改它,就是往只读内存里写数据——操作系统会直接给你一个段错误。
我在项目中遇到过类似的情况。有一次调试一个通信协议解析模块,发现只要收到特定报文,系统就重启。查了两天才发现,是有人把配置字符串直接当字面量传进去,然后在函数里改了它。
正确的做法是什么?
char str[] = "hello"; // 可修改的数组
str[0] = 'H'; // 没问题
或者用 const 明确声明:
const char *p = "hello"; // 明确告诉编译器:这是只读的
暗礁二:字符串缺少空字符终止
C语言的字符串,本质上就是一个以 '\0' 结尾的字符数组。没有这个终止符,字符串就不是字符串,而是一堆乱码。
看这个例子:
char buf[5] = {'H', 'e', 'l', 'l', 'o'};
printf("%s", buf); // 输出:Hello???... 后面跟着一堆垃圾
为什么?因为 buf 没有 '\0' 结尾。printf 会一直读下去,直到在内存中碰到一个 '\0' 为止。你永远不知道它会读到什么。
我曾经调试过一个串口打印乱码的问题。排查到最后,发现是协议栈里一个字符串缓冲区初始化时忘了清零,导致字符串末尾没有终止符。打印出来的数据后面总是跟着一些奇怪的字节。
正确的做法:
char buf[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 手动加终止符
// 或者更简单:
char buf[] = "hello"; // 编译器自动加 '\0'
暗礁三:strcpy 和 strcat 的缓冲区溢出
这是C语言字符串操作里最臭名昭著的陷阱。没有之一。
char dest[10];
char *src = "This is a very long string that will overflow the buffer";
strcpy(dest, src); // 缓冲区溢出!
strcpy 不会检查目标缓冲区的大小。它只管把源字符串一个一个字节地复制过去,直到遇到 '\0'。如果源字符串比目标缓冲区长,它就会写到缓冲区之外的内存里。
后果是什么?
- 覆盖其他变量的值
- 破坏函数返回地址(经典的栈溢出攻击)
- 程序崩溃
- 安全漏洞
strcat 也一样:
char buf[10] = "Hello";
strcat(buf, " World!!!"); // 溢出!
它会在 buf 的末尾追加字符串,同样不检查空间够不够。
那怎么办?用带长度限制的版本:
char dest[10];
char *src = "This is a very long string";
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 确保终止
strncat 也一样:
char buf[10] = "Hello";
strncat(buf, " World!!!", sizeof(buf) - strlen(buf) - 1);
char dest[10];
snprintf(dest, sizeof(dest), "%s", src); // 安全,不会溢出
知识体系总览
下面这张图总结了字符串操作的三大陷阱和对应的规避方法:
总结一下
字符串操作看似简单,但三个陷阱个个致命:
| 陷阱 | 表现 | 规避方法 |
|---|---|---|
| 字面量不可修改 | 运行时崩溃 | 用数组存储,或用 const 声明 |
| 缺少 '\0' 终止 | 乱码、越界读取 | 确保缓冲区以 '\0' 结尾 |
| 缓冲区溢出 | 数据破坏、崩溃、安全漏洞 | 使用带长度限制的函数 |
说实话,这些坑我几乎都踩过。每次踩完都告诉自己:下次一定注意。但人嘛,总有疏忽的时候。所以最好的办法,就是养成好习惯——写代码时多问自己一句:这个字符串从哪里来?到哪里去?缓冲区够不够大?
嗯,今天就聊到这儿。记住一句话:字符串操作,安全第一。
公众号:蓝海资料掘金营,微信deep3321