C语言字符串函数使用避坑指南:从strcat内存错误到自定义实现
许多C语言初学者在调用标准库函数如strcat时,容易混淆“字符数组”与“字符串常量指针”的内存属性差异,进而引发运行时崩溃。理解标准库的输入输出约束,并掌握基于动态内存分配的自定义拼接方案,是编写健壮C代码的关键。
问题现象:类型正确为何运行崩溃?
在处理字符串拼接时,strcat 是最常用的工具。其原型定义如下:
extern char *strcat(char *dest, char *src);
该函数功能是将 src 所指字符串追加到 dest 结尾(覆盖 dest 末尾的 '\0'),并重新添加结束符。src 和 dest 所指内存区域不可重叠,且 dest 必须有足够空间容纳结果。通常返回指向 dest 的指针。
场景一:正常使用
当 dest 定义为大小充足的字符数组时,程序运行正常:
#include <stdio.h>
#include <string.h>
int main() {
char d[30] = "Golden Global"; // 开辟可修改的内存空间
const char *s = " View"; // 源字符串指针
strcat(d, s);
printf("%s", d); // 输出: Golden Global View
return 0;
}
场景二:隐蔽的错误
如果将 dest 也声明为指向字符串常量的指针,即使类型匹配,程序也会执行失败:
#include <stdio.h>
#include <string.h>
int main() {
const char *p = "Golden Global"; // 指向只读内存区
const char *s = " View";
// 尝试修改只读内存,导致 Segmentation Fault
strcat(p, s);
printf("%s", p);
return 0;
}
虽然两个参数都是指针,但 p 指向的是编译期确定的常量字符串区(通常在数据段或代码段),通常是只读的。而 strcat 内部需要写入目标地址,从而触发非法访问。
深度解析:strcat 的底层实现逻辑
通过查看标准库中 strcat 的典型实现源码,可以清晰看出其内存操作行为:
char *strcat(char *strDest, const char *strScr) {
char *address = strDest;
assert((strDest != NULL) && (strScr != NULL));
// 1. 移动 dest 指针至字符串末尾 '\0'
while(*strDest) {
strDest++;
}
// 2. 循环拷贝 src 内容至 dest,直到遇到 '\0'
while(*strDest++ = *strScr++) {
; // 空循环体
}
return address;
}
关键步骤 *strDest++ = *strScr++ 表明函数需要对 strDest 指向的地址进行赋值写操作。若 strDest 指向常量区或未被分配的野指针,编译器虽可能不报错,但运行时会因权限不足或内存无效而崩溃。数组名作为参数传递时,底层仍是地址,但该地址对应的是栈上预先分配好的连续可变内存,因此合法。
解决方案:自定义安全的字符串拼接函数
若希望像 C# 或其他高级语言那样,直接传入两个字符串指针即可得到拼接后的新字符串,需要手动管理内存。以下是自实现的 strcatDemo,利用 malloc 动态分配足够空间:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
// 自定义拼接函数
char *strcatDemo(const char *str1, const char *str2) {
// 1. 参数校验
assert(str1 != NULL && str2 != NULL);
// 2. 计算总长度并分配内存 (需额外1字节存储 \0)
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char *result = (char *)malloc(len1 + len2 + 1);
if (result == NULL) {
return NULL; // 内存分配失败处理
}
char *ptr = result;
// 3. 拷贝第一部分
while(*str1) {
*ptr++ = *str1++;
}
// 4. 拷贝第二部分
while(*str2) {
*ptr++ = *str2++;
}
// 5. 添加结束符
*ptr = '\0';
return result;
}
int main() {
const char *p = "hello, ";
const char *s = "world!";
char *t = strcatDemo(p, s);
if (t) {
puts(t); // 输出: hello, world!
free(t); // 重要:释放动态内存
}
return 0;
}
此实现模拟了类似 C# 中字符串相加的特性,返回的新字符串独立于原字符串,使用者负责后续 free 释放内存,避免了目标缓冲区溢出或只读冲突的风险。
附录:常见字符串函数原型参考
为避免未来开发中出现类似问题,建议熟悉以下三个核心函数的逻辑:
1. strcpy:字符串复制
注意必须在复制完成后显式添加结束符,以防源串短于目的串残留旧数据。
char *strcpy(char *strDest, const char *strScr) {
char *address = strDest;
assert((strDest != NULL) && (strScr != NULL));
while(*strScr) {
*strDest++ = *strScr++;
}
*strDest = '\0'; // 关键步骤
return address;
}
2. strcmp:字符串比较
逐个字符比较 ASCII 码,返回差值。
int strcmp(const char *str1, const char *str2) {
assert(str1 != NULL && str2 != NULL);
while(*str1 && *str2 && (*str1 == *str2)) {
str1++;
str2++;
}
return *str1 - *str2;
}
3. strlen:长度计算
计算直到 '\0' 前的字符数。
size_t strlen(const char *str) {
assert(str != NULL);
size_t len = 0;
while(*str++) {
len++;
}
return len;
}
以上就是关于C语言字符串函数 strcat 常见陷阱分析与自定义实现的详细内容,更多关于C语言基础与内存管理的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













