首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >字符串连接函数strcat()和strncat()详解

字符串连接函数strcat()和strncat()详解

作者头像
byte轻骑兵
发布2026-01-20 17:08:58
发布2026-01-20 17:08:58
1360
举报

在 C 语言开发中,字符串操作是基础且高频的需求,而字符串拼接(将一个字符串追加到另一个字符串末尾)更是核心场景之一。C 标准库提供的strcat()和strncat()函数,是实现这一功能的常用工具,但二者在安全性、适用场景上差异显著。不少开发者(尤其是初学者)容易混淆二者逻辑,或忽略潜在风险(如缓冲区溢出),导致程序出现未定义行为甚至安全漏洞。

一、函数简介

字符串连接的本质,是将 “源字符串”(src)的字符序列,从 “目标字符串”(dest)的终止符'\0'位置开始写入,最终形成一个新的连续字符串(仍以'\0'结尾)。C 标准库的strcat()和strncat()均服务于这一目标,但设计思路不同:

  • strcat():全称 “string concatenate”,意为 “字符串拼接”。它会将源字符串src的全部字符(从首字符到'\0')完整追加到目标字符串dest的末尾,最终自动在拼接结果后补充'\0'。但该函数不检查目标缓冲区的剩余空间,若dest空间不足,会直接导致缓冲区溢出。
  • strncat():在strcat()基础上增加了 “长度限制”(n),全称可理解为 “string concatenate with length limit”。它仅会从src中追加最多 n 个字符(或追加到src的'\0'为止,取二者最小值),且无论追加多少字符,最终都会在结果末尾强制补充'\0'。这种设计大幅降低了缓冲区溢出风险,安全性更高。

二者均属于<string.h>头文件,需包含该头文件才能使用;且返回值均为目标字符串dest的起始地址,支持链式调用(如printf("%s", strcat(dest, src)))。

二、函数原型

函数原型是理解函数用法的核心,需明确每个参数的含义、类型及约束条件,避免因参数误用导致错误。

1. strcat () 原型

代码语言:javascript
复制
char *strcat(char *dest, const char *src);

参数 1:char dest

  • 目标字符串的首地址,要求是可修改的字符数组 / 缓冲区(不能是字符串常量,如"Hello",因为常量存于只读内存区,修改会触发未定义行为)。dest必须以'\0'结尾(否则函数无法找到追加的起始位置),且需有足够空间容纳拼接后的字符串(含新的'\0')。

参数 2:const char src

  • 源字符串的首地址,const修饰表示函数不会修改src的内容(符合 “源字符串只读” 的逻辑)。src必须以'\0'结尾(否则函数会持续读取src后续内存,直到找到'\0',导致 “读取越界”)。

返回值:char

  • 返回dest的起始地址(而非拼接后的末尾地址),方便后续对dest进行链式操作(如连续拼接:strcat(strcat(dest, src1), src2)`)。

2. strncat () 原型

代码语言:javascript
复制
char *strncat(char *dest, const char *src, size_t n);

前两个参数:与strcat()完全一致,约束条件相同(dest可修改、以'\0'结尾;src只读)。 参数 3:size_t n

  • 最大追加字符数(无符号整数类型,size_t本质是unsigned int或unsigned long,取决于编译器)。函数会从src中读取字符,直到满足以下两个条件之一:① 已读取n个字符;② 遇到src的'\0'。注意:n 不包含最终补充的'\0',即即使追加了n个字符,函数仍会额外添加一个'\0'作为终止符。

返回值:与strcat()一致,返回dest的起始地址。

三、函数实现

理解函数的实现逻辑,能帮我们更深刻地掌握其行为(如为何strcat()会溢出、strncat()为何安全)。以下伪代码基于 C 标准库的设计思路,简化了边界检查(实际库函数会有更严谨的错误处理),聚焦核心流程。

1. strcat () 实现伪代码

代码语言:javascript
复制
// 模拟strcat()逻辑:将src全部追加到dest末尾
char *my_strcat(char *dest, const char *src) {
    // 1. 检查空指针(实际库函数可能直接崩溃,此处简化处理)
    if (dest == NULL || src == NULL) {
        return NULL; // 空指针直接返回错误
    }

    // 2. 保存dest的起始地址(用于最终返回)
    char *temp = dest;

    // 3. 找到dest的终止符'\0'(定位追加起始位置)
    while (*dest != '\0') {
        dest++; // 指针后移,直到指向'\0'
    }

    // 4. 将src的字符逐个复制到dest末尾(包括src的'\0')
    while (*src != '\0') {
        *dest = *src; // 复制当前字符
        dest++;       // dest指针后移
        src++;        // src指针后移
    }

    // 5. 确保拼接后以'\0'结尾(实际上述循环已复制src的'\0',此处冗余但安全)
    *dest = '\0';

    // 6. 返回dest的起始地址
    return temp;
}

核心逻辑总结:先 “找 dest 的末尾”,再 “复制 src 全量字符”,最后 “补终止符”。因未检查dest剩余空间,若src长度超过dest剩余容量,会直接覆盖dest后续内存(缓冲区溢出)。

2. strncat () 实现伪代码

代码语言:javascript
复制
// 模拟strncat()逻辑:最多追加n个字符到dest末尾
char *my_strncat(char *dest, const char *src, size_t n) {
    // 1. 空指针或n=0直接返回dest(n=0时不追加)
    if (dest == NULL || src == NULL || n == 0) {
        return dest;
    }

    // 2. 保存dest起始地址
    char *temp = dest;

    // 3. 找到dest的终止符'\0'
    while (*dest != '\0') {
        dest++;
    }

    // 4. 复制最多n个字符(或到src的'\0'为止)
    size_t count = 0; // 已复制的字符数
    while (count < n && *src != '\0') {
        *dest = *src;
        dest++;
        src++;
        count++; // 计数+1,避免超过n
    }

    // 5. 强制补充'\0'(关键!无论是否复制满n个字符)
    *dest = '\0';

    // 6. 返回dest起始地址
    return temp;
}

核心逻辑总结:在strcat()基础上增加 “计数控制”(count < n),且无论复制多少字符,强制补'\0'—— 这是strncat()安全性的核心:即使src无'\0',也不会无限读取;即使复制满n个字符,也不会遗漏终止符。

四、使用场景:何时选 strcat (),何时选 strncat ()?

二者的适用场景需结合 “源字符串长度是否确定”“目标缓冲区空间是否可控” 两个核心因素判断,避免盲目使用。

4.1 strcat () 的适用场景

仅当源字符串长度已知,且目标缓冲区剩余空间足够容纳源字符串时,才考虑使用strcat()—— 此时它的 “简洁性” 能简化代码,无需额外计算长度。

典型场景:

  • 固定长度的字符串拼接:如拼接两个已知内容的常量字符串(如"Hello, "和"World!"),且目标缓冲区已提前分配足够空间。

示例:char dest[20] = "Hello, "; char src[] = "World!"; strcat(dest, src);(src长度 6,dest初始长度 7,剩余空间 13,足够容纳)。

  • 调试日志拼接:调试时拼接已知格式的日志前缀和固定信息(如"Debug: "和"Init success"),且日志缓冲区大小已预留充足。
  • 连续拼接已知长度的字符串:如拼接多个已知长度的子串(如src1长度 3、src2长度 4),且目标缓冲区总容量 = 初始长度 + sum (子串长度) + 1(留'\0')。

4.2 strncat () 的适用场景

源字符串长度不确定(如用户输入、网络数据、文件读取),或目标缓冲区空间有限时,必须优先使用strncat()—— 它的 “长度限制” 能避免缓冲区溢出,是安全性的关键。

典型场景:

  • 处理用户输入:用户输入的字符串长度不可控(如控制台输入、表单提交),需限制追加长度,防止恶意输入导致溢出。

示例:用户输入昵称(最长 10 字符),追加到"User: "后,用strncat(dest, input, 10)确保最多追加 10 个字符。

  • 网络数据 / 文件数据拼接:从网络或文件读取的字符串可能无终止符(如数据包截断、文件损坏),strncat()的 “n 限制” 能避免无限读取,防止程序崩溃。
  • 缓冲区空间有限的场景:如嵌入式开发中,内存资源紧张(目标缓冲区仅 100 字节),需严格控制追加长度,避免占用其他内存区域。
  • 拼接未知长度的动态字符串:如从数据库查询的字符串(长度可能变化),需根据目标缓冲区剩余空间计算n(n = 缓冲区总大小 - 当前长度 - 1),确保不溢出。

五、注意事项

C 语言字符串操作的 “坑” 多集中在 “终止符”“内存边界”“类型匹配” 上,strcat()和strncat()也不例外,需逐一规避。

5.1 通用注意事项(二者均需遵守)

(1)目标字符串不能是字符串常量

strcat()和strncat()都会修改dest的内容,若dest是字符串常量(如char *dest = "Hello";),则存于只读内存区(如 Linux 的.rodata段),修改会触发 “段错误(Segmentation Fault)”。

错误示例:

代码语言:javascript
复制
char *dest = "Hello"; // 字符串常量,只读

char src[] = "World";

strcat(dest, src); // 错误:修改只读内存,触发段错误

正确做法:dest必须是可修改的字符数组,如char dest[20] = "Hello";。

(2)目标字符串必须以'\0'结尾

若dest无'\0'(如char dest[10] = {'H','e','l','l','o'};,未初始化剩余空间),函数会持续向后查找'\0',导致 “读取越界”(访问dest外的内存),触发未定义行为(程序崩溃、乱码)。

错误示例:

代码语言:javascript
复制
char dest[10] = {'H','e','l','l','o'}; // 无'\0',剩余空间随机值

char src[] = "World";

strcat(dest, src); // 错误:找不到'\0',读取越界

正确做法:初始化时显式加'\0',或用字符串常量初始化(自动补'\0'):char dest[10] = "Hello";(自动在第 5 位加'\0')。

(3)避免源字符串与目标字符串内存重叠

若src和dest的内存区域重叠(如dest是"ABCDEF",src是dest+2,即"CDEF"),拼接时会覆盖src的未读取部分,导致结果错误。

错误示例:

代码语言:javascript
复制
char dest[20] = "ABCDEF";

char *src = dest + 2; // src指向"CD EF"(内存重叠)

strcat(dest, src); // 预期"ABCDEFCD EF",实际会覆盖src,结果乱码

正确做法:确保src和dest内存不重叠,若需重叠拼接,需用临时缓冲区中转。

5.2 strcat () 专属注意事项:警惕缓冲区溢出

strcat()的最大风险是无长度检查,若dest剩余空间 < src长度(含'\0'),会直接覆盖dest后续内存,可能导致:

  • 程序崩溃(覆盖函数栈帧、返回地址);
  • 数据损坏(覆盖其他变量的值);
  • 安全漏洞(缓冲区溢出攻击,篡改程序执行流程)。

错误示例(溢出风险):

代码语言:javascript
复制
char dest[10] = "Hello, "; // 长度7(含'\0'),剩余空间3

char src[] = "World!"; // 长度6(含'\0'),需6字节空间

strcat(dest, src); // 错误:剩余空间3 < 6,缓冲区溢出

运行结果:dest会覆盖后续内存,可能输出乱码,或直接崩溃。

5.3 strncat () 专属注意事项:别误解 n 的含义

(1)n 是 “最大追加字符数”,不是 “最终总长度”

n仅限制从src中读取的字符数(不含'\0'),最终dest的总长度 = 初始长度 + 实际追加字符数 + 1('\0')。若误将n设为 “目标总长度”,会导致空间不足。

错误示例:

代码语言:javascript
复制
char dest[10] = "Hi, "; // 初始长度3(含'\0'),目标总长度10

strncat(dest, src, 10); // 错误:n=10表示最多追加10个字符,总长度会超10

正确做法:n = 缓冲区总大小 - 初始长度 - 1(留'\0'空间):

代码语言:javascript
复制
size_t max_add = sizeof(dest) - strlen(dest) - 1; // 10-3-1=6

strncat(dest, src, max_add); // 最多追加6个字符,总长度3+6+1=10,刚好

(2)n=0 时不追加任何字符,但仍会补'\0'

若n=0,strncat()仅会在dest的当前'\0'位置重新写一个'\0'(无实际影响),不会修改src或dest的其他内容。

示例:

代码语言:javascript
复制
char dest[20] = "Test";

strncat(dest, "ABC", 0);

printf("%s", dest); // 输出"Test",无变化

(3)src 无'\0'时,最多追加 n 个字符

若src是无'\0'的字符数组(如char src[] = {'A','B','C'};),strncat()会读取最多n个字符,不会无限读取(避免越界),且最终补'\0'。

示例:

代码语言:javascript
复制
char dest[10] = "Data: ";

char src[] = {'A','B','C'}; // 无'\0'

strncat(dest, src, 5); // 最多读5个,但src仅3个,实际追加3个

printf("%s", dest); // 输出"Data: ABC"(自动补'\0')

六、示例代码:实战演示与错误对比

结合实际场景编写示例,对比正确与错误用法,直观理解函数的使用方式。

1. strcat () 示例:正确用法与错误示范

(1)正确用法:固定长度拼接

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // 目标缓冲区:大小20,初始内容"Hello, "(长度7,含'\0')
    char dest[20] = "Hello, ";
    // 源字符串:长度6("World!" + '\0'),dest剩余空间13,足够容纳
    char src[] = "World!";

    // 调用strcat()拼接
    char *result = strcat(dest, src);

    // 输出结果(result与dest地址相同)
    printf("拼接结果:%s\n", result);
    printf("dest地址:%p,result地址:%p\n", dest, result);

    return 0;
}

输出结果

代码语言:javascript
复制
拼接结果:Hello, World!

dest地址:0x7ffeefbff460,result地址:0x7ffeefbff460

(2)错误示范:缓冲区溢出

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // 目标缓冲区:大小10,初始内容"Hello, "(长度7)
    char dest[10] = "Hello, ";
    // 源字符串:长度10("LongString" + '\0'),dest剩余空间3,不足容纳
    char src[] = "LongString";

    // 错误:src长度10 > dest剩余空间3,缓冲区溢出
    strcat(dest, src);

    // 输出结果:可能乱码或程序崩溃(未定义行为)
    printf("拼接结果:%s\n", dest);

    return 0;
}

运行风险:dest仅剩余 3 字节空间,src需 10 字节,会覆盖dest后续内存,可能导致:

  • 输出乱码(如Hello, Lon烫烫烫);
  • 程序崩溃(覆盖函数栈帧);
  • 其他变量被篡改(若dest后有其他变量)。

2. strncat () 示例:安全处理用户输入

(1)基础用法:限制追加长度

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // 目标缓冲区:"User: "(长度6,含'\0'),总大小20
    char dest[20] = "User: ";
    // 源字符串:长度15(超过缓冲区剩余空间)
    char src[] = "ThisIsALongUsername";
    // 计算最大可追加长度:总大小 - 初始长度 - 1(留'\0')
    size_t max_add = sizeof(dest) - strlen(dest) - 1; // 20-6-1=13

    // 调用strncat(),最多追加13个字符
    strncat(dest, src, max_add);

    printf("拼接结果:%s\n", dest);
    printf("拼接后长度:%zu\n", strlen(dest));

    return 0;
}

输出结果

代码语言:javascript
复制
拼接结果:User: ThisIsALongUse

拼接后长度:19(6+13,留1字节'\0')

关键:通过max_add计算剩余空间,确保不溢出 —— 即使src更长,也仅追加 13 个字符。

(2)实战场景:处理用户输入

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

#define MAX_INPUT 10 // 用户输入最大长度
#define DEST_SIZE 20 // 目标缓冲区大小

int main() {
    char dest[DEST_SIZE] = "Welcome, ";
    char user_input[MAX_INPUT + 1]; // 存用户输入,+1留'\0'

    // 提示用户输入
    printf("请输入你的昵称(最多10个字符):");
    // 读取用户输入(限制10个字符,避免溢出)
    fgets(user_input, sizeof(user_input), stdin);

    // 移除fgets()读取的换行符(若有)
    size_t input_len = strlen(user_input);
    if (input_len > 0 && user_input[input_len - 1] == '\n') {
        user_input[input_len - 1] = '\0';
    }

    // 计算最大可追加长度
    size_t max_add = DEST_SIZE - strlen(dest) - 1;
    // 追加用户输入,最多max_add个字符
    strncat(dest, user_input, max_add);

    printf("最终欢迎语:%s\n", dest);

    return 0;
}

运行示例

  • 若用户输入"ZhangSan"(8 字符):输出"Welcome, ZhangSan";
  • 若用户输入"WangWu123456"(12 字符,超 MAX_INPUT):fgets()仅读 10 字符,strncat()追加 10 字符,输出"Welcome, WangWu1234"(无溢出)。

七、差异对比:strcat () 与 strncat () 核心区别

为更清晰地梳理二者差异,下表从 7 个核心维度进行对比,方便快速查阅:

对比维度

strcat()

strncat()

函数参数

2 个(dest, src)

3 个(dest, src, n)

追加长度逻辑

追加 src 全部字符(直到 '\0')

追加最多 n 个字符,或到 src 的 '\0' 为止

缓冲区溢出风险

无长度检查,风险高

有 n 限制,风险低(需正确设 n)

对 src 的依赖

必须以 '\0' 结尾,否则读取越界

可无 '\0',最多读 n 个字符

终止符处理

复制 src 的 '\0' 作为终止符

强制添加 '\0',无论复制多少字符

适用场景

源长度已知、dest 空间足够

源长度未知、dest 空间有限

代码复杂度

简洁,无需计算长度

需计算 max_add(n 的合理值)


博主简介 byte轻骑兵,现就职于国内知名科技企业,专注于嵌入式系统研发,深耕 Android、Linux、RTOS、通信协议、AIoT、物联网及 C/C++ 等领域。乐于技术分享与交流,欢迎关注互动! 📌 主页与联系方式

  • CSDN:https://blog.csdn.net/weixin_37800531
  • 知乎:https://www.zhihu.com/people/38-72-36-20-51
  • 微信公众号:嵌入式硬核研究所
  • 邮箱:byteqqb@163.com(技术咨询或合作请备注需求)

⚠️ 版权声明 本文为原创内容,未经授权禁止转载。商业合作或内容授权请联系邮箱并备注来意。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、函数简介
  • 二、函数原型
  • 三、函数实现
  • 四、使用场景:何时选 strcat (),何时选 strncat ()?
    • 4.1 strcat () 的适用场景
    • 4.2 strncat () 的适用场景
  • 五、注意事项
    • 5.1 通用注意事项(二者均需遵守)
    • 5.2 strcat () 专属注意事项:警惕缓冲区溢出
    • 5.3 strncat () 专属注意事项:别误解 n 的含义
  • 六、示例代码:实战演示与错误对比
  • 七、差异对比:strcat () 与 strncat () 核心区别
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档