C语言安全的进行字符串拷贝的几种方法

 更新时间:2026年06月16日 08:56:35   作者:码农爱学习  
在C语言编程中,字符串是通过定义char数组来保存,并通过指针以及字符串拷贝函数来实现字符串的拷贝,在字符串长度不太明确的情况下,可能处出现拷贝越界的情况,给程序引入了不安全的因素,本篇就来讨论下,在C语言中,如何安全的使用strcpy这类函数进行字符串的拷贝

引言

在C语言编程中,字符串是通过定义char数组来保存,并通过指针以及字符串拷贝函数(如strcpy)来实现字符串的拷贝,无法方便的像C++中使用等号直接对std::string类型的字符串赋值。

使用strcpy这类接口,在字符串长度不太明确的情况下,可能处出现拷贝越界的情况,给程序引入了不安全的因素。本篇就来讨论下,在C语言中,如何安全的使用strcpy这类函数进行字符串的拷贝。

1 strcpy

strcpy是C语言标准库中的一个最基础的字符串处理函数,可以把源字符串复制到目标字符串。

1.1 函数原型

char *strcpy(char *dest, const char *src);

src 所指向的以空字符('\0')结尾的字符串复制到 dest 所指向的数组中,同时会复制终止空字符

参数

  • dest 是目标字符串的指针
  • src 是源字符串的指针

返回值

  • 返回目标字符串的指针

1.2 使用示例

一个基础的strcpy使用示例,需要确保目标数组足够大

// gcc strcpy1.c -o strcpy
#include <stdio.h>
#include <string.h>

int main() 
{
    char src[] = "Hello";
    char dest[10] = {0}; // 确保目标数组足够大

    strcpy(dest, src);
    printf("复制后的字符串: %s\n", dest);
    
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }

    return 0;
 }

运行结果如下:

注意事项

  1. 目标数组大小要足够strcpy 不会检查目标数组的大小,一旦目标数组的空间不足以容纳源字符串,就会导致缓冲区溢出,这是非常危险的,可能会引发程序崩溃或者产生安全漏洞。
  2. 源字符串必须以空字符结尾:如果源字符串没有以 '\0' 结尾,strcpy 会一直复制内存中的数据,直到遇到空字符为止,这会造成未定义行为。
  3. 避免自我复制:不要将字符串复制到自身,否则会导致数据被破坏。

1.3 拷贝越界情况举例

测试一下,如果要拷贝的字符串长度大于目标存储空间,会是什么结果。

// gcc strcpy2.c -o strcpy2
#include <stdio.h>
#include <string.h>

int main() 
{
    char src[] = "Hello, World"; //原字符串11个字符,再加上结尾'\0'则占12个字符
    char dest[10] = {0}; //目标存储空间只有10个
    char other[10] = {0}; //随后再定一个10个大小的other来验证是否被越界拷贝了

    strcpy(dest, src);
    printf("复制后的字符串: %s\n", dest);
    
    printf("dest addr:%p\n", dest);
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }
    
    printf("other addr:%p\n", other);
    size_t bytes = (char*)other - (char*)dest;
    printf("other (other-dest):%zu\n", bytes);
    for (int i=0; i<sizeof(other); i++)
    {
        printf("other[%d]:%x(%c)\n", i, other[i], other[i]);
    }

    return 0;
 }

运行结果如下:

需要注意的是,虽然dest字符串通过printf正常输出了,但实际是拷贝字符时,越界拷贝,虽然这里暂时没有问题,但other中的内容被篡改,如果后续需要使用other中的内容,可能就会出现不符合预期的结果。

1.4关于是否会加上结尾符的验证

// gcc strcpy3.c -o strcpy3
#include <stdio.h>
#include <string.h>

int main() 
{
    char src[] = "Hello";
    char dest[10] = {0}; // 确保目标数组足够大

    strcpy(dest, "12345abcd");
    printf("复制后的字符串: %s\n", dest);
    
    strcpy(dest, src);
    printf("复制后的字符串: %s\n", dest);
    
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }
    
    //再测试一下char数组逐个赋值后的拷贝(确保src2当做字符串时最后没有‘\0')
    char src2[2];
    src2[0] = 'm';
    src2[1] = 'n';
    strcpy(dest, src2);
    printf("复制后的字符串: %s\n", dest);
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }

    return 0;
 }

运行结果如下:

可以看到:

  • 拷贝src字符串时,"Hello"被正常拷贝,后面一个’\0’也拷贝了,dest中剩下的则保持原样
  • 拷贝char src2[2]时,由于src2没有自动被赋予的’\0’字符串结尾符,在strcpy赋值时,就只是复制了2个char数据本身,所有在printf打印时,就和后面的数据一起打印出来了,直到遇到了’\0’字符。
  • 另外有点特殊的是,dest原有的内容,被整体向后移动了。

2 strncpy

strncpy是 strcpy的安全版本,用于复制指定长度的字符串。

2.1 函数原型

char *strncpy(char *dest, const char *src, size_t n);

src 的前 n 个字符复制到 dest不自动添加终止符 '\0'(除非 src 的长度小于 n)。

参数

  • dest:目标字符串指针(需提前分配足够空间)。
  • src:源字符串指针(必须以 '\0' 结尾)。
  • n:最多复制的字符数。

返回值

  • 返回目标字符串的指针,也就是dest

它会复制最多 n 个字符,能够防止缓冲区溢出。

不过要注意,如果源字符串的长度超过 ndest 数组将不会以空字符结尾。

2.2 使用示例

// gcc strncpy1.c -o strncpy1
#include <stdio.h>
#include <string.h>

int main() 
{
    char src[] = "Hello";
    char dest[10] = {0}; // 确保目标数组足够大

    int destSize = sizeof(dest);
    strncpy(dest, src, destSize-1); //最多只能复制destSize-1, 因为要加上结尾符
    dest[destSize] = '\0'; //手动加上结尾符
    printf("复制后的字符串: %s\n", dest);
    
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }

    return 0;
 }

运行结果如下:

关键注意事项

是否自动添加终止符:

  • 如果 src 的长度 小于 nstrncpy 会在复制完 src 后,在 dest 中填充 '\0' 直到 n 个字符。
  • 如果 src 的长度 大于等于 ndest 不会自动添加终止符,此时需要手动添加 dest[n-1] = '\0',否则可能导致字符串处理异常。

避免缓冲区溢出

n 应不超过 dest 的大小(包括终止符空间)。

例如:char dest[5]; strncpy(dest, src, 5); 可能导致溢出(因为 dest 的有效空间只有 4 个字符 + 1 个终止符)

2.3 拷贝越界被截断的情况举例

// gcc strncpy2.c -o strncpy2
#include <stdio.h>
#include <string.h>

int main() 
{
    char src[] = "Hello, World"; //原字符串11个字符,再加上结尾'\0'则占12个字符
    char dest[10] = {0}; //目标存储空间只有10个
    char other[10] = {0}; //随后再定一个10个大小的other来验证是否被越界拷贝了

    int destSize = sizeof(dest);
    strncpy(dest, src, destSize-1);
    dest[destSize] = '\0';
    printf("复制后的字符串: %s\n", dest);
    
    printf("dest addr:%p\n", dest);
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }
    
    printf("other addr:%p\n", other);
    size_t bytes = (char*)other - (char*)dest;
    printf("other (other-dest):%zu\n", bytes);
    for (int i=0; i<sizeof(other); i++)
    {
        printf("other[%d]:%x(%c)\n", i, other[i], other[i]);
    }

    return 0;
 }

运行结果如下:

可以看到,使用strnpy,通过指定拷贝的长度:

  • 数据如果超出长度,则被截断,并且有结尾符
  • 确保了其它数据不被越界访问

2.4 不规范的使用举例

// gcc strncpy3.c -o strncpy3
#include <stdio.h>
#include <string.h>

int main() 
{
    char src[] = "Hello";
    char dest[10] = {0}; //拷贝的目标位置
    char other[10] = "xy"; //随后一个位置用于测试
    printf("other[0]: %c\n", other[0]);
    
    printf("dest addr:%p\n", dest);
    printf("other addr:%p\n", other);
    size_t bytes = (char*)other - (char*)dest;
    printf("other (other-dest):%zu\n", bytes); //确认other就是在dest之后

    int destSize = sizeof(dest);
    //不规范1:这里的原字符串长度大于destSize,并且没有手动添加字'\0'结尾符
    strncpy(dest, "12345abcdefg", destSize);
    printf("复制后的字符串: %s\n", dest); //dest与后面的other连成一个字符串了!
    printf("other[0]: %c\n", other[0]); //由于strncpy拷贝了destSize,后面的other没有影响
    
    //不规范2:这次拷贝的字符串长度小于destSize, 并且没有手动添加字'\0'结尾符
    strncpy(dest, src, 5);
    printf("复制后的字符串: %s\n", dest);
    
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }
    
    int srcSize = sizeof(src); //注意src的sizeof是包含了之后的'\0'结尾符的,所以是6
    int srcLen = strlen(src); 
    printf("srcSize:%d, srcLen:%d\n", srcSize, srcLen);
    strncpy(dest, src, srcSize);
    printf("复制后的字符串: %s\n", dest);
    
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }

    return 0;
 }

运行结果如下:

strncpy的安全性只是在于拷贝的长度可控,避免越界访问。

但当要拷贝的数据长度大于目标空间时,数据被截断,但没有提示,如果直接使用截断的字符串,也会出现意想不到的的问题。

3 strlcpy

strlcpy是一个更安全的字符串复制函数,并可以返回实际拷贝的长度。

不过需要注意的是,strlcpy 并非标准 C 库函数,而是在 BSD 系统(如 macOS)中存在,

Linux 系统需要通过 #include <bsd/string.h> 引入,并额外链接 libbsd,另外也要安装一下这个库:

sudo apt-get install libbsd-dev

3.1 函数原型

size_t strlcpy(char *dest, const char *src, size_t size);

src 的内容复制到 dest,确保 dest'\0' 结尾,并返回 src 的原始长度(不包含终止符)。

参数

  • dest:目标字符串指针(需提前分配空间)。
  • src:源字符串指针(必须以 '\0' 结尾)。
  • sizedest 的最大容量(包括终止符 '\0')。

返回值

  • src 的实际长度(不包含 '\0')。若返回值 ≥ size,说明复制时发生了截断。

3.2 使用示例

// gcc strlcpy.c -o strlcpy -lbsd
#include <stdio.h>
#include <string.h>
#include <bsd/string.h>  // Linux需要显式包含

int main() 
{
    char src[] = "Hello, World";
    char dest[10] = {0}; 
    char other[10] = {0};

    size_t len = strlcpy(dest, src, sizeof(dest));
    printf("strlcpy ret:%zu\n", len);
    if (len > sizeof(dest))
    {
        printf("copy oversize!\n");
    }
    printf("复制后的字符串: %s\n", dest);
    
    printf("dest addr:%p\n", dest);
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }
    
    printf("other addr:%p\n", other);
    size_t bytes = (char*)other - (char*)dest;
    printf("other (other-dest):%zu\n", bytes);
    for (int i=0; i<sizeof(other); i++)
    {
        printf("other[%d]:%x(%c)\n", i, other[i], other[i]);
    }

    return 0;
 }

运行结果:

strlcpy的特性

自动处理终止符

  • strlcpy 会确保 dest'\0' 结尾,即使 src 被截断。
  • 最多复制 size - 1 个字符到 dest,并在末尾添加 '\0'

安全避免溢出

  • 无论 src 多长,dest 都不会溢出。
  • src 长度 ≥ size,函数会截断 src,仅复制 size - 1 个字符。

返回值的用途

  • 返回src的实际长度,可用于检测截断情况:
if (strlcpy(dest, src, size) >= size) {
    printf("警告:源字符串被截断!\n");
}

4 上述3种函数对比

函数是否自动添加终止符溢出风险截断处理
strcpy不截断,可能溢出
strncpy截断但不补终止符
strlcpy截断并补终止符
  • strcpy使用简单,在数据长度不确定的情况下使用会有风险
  • strnpy指定了拷贝长度吗,但仍有数据被截断的风险
  • strlcpy可以通过返回值检测是否被截断,但需要额外库的支持

5 自定义strlcpy

基于上述分析,可以对strnpy进行自定义封装改造,实现类似strlcpy,这样也不需要额外库的支持。

5.1 对strnpy进行封装实现自定义检查

int my_strlcpy(char *dst, const char *src, size_t dstSize) 
{
    size_t srcLen = strlen(src);
    
    if (NULL == dst || NULL == src || 0 == dstSize)
    {
        return -1; //参数错误
    }
    
    size_t maxCopyLen = dstSize - 1;
    strnpy(dst, src, maxCopyLen);
    dst[len] = '\0';
    
    if (srclen > maxCopyLen)
    {
        printf("%d > %d, copy oversize! only copy:%s\n", srclen, maxCopyLen, dst);
        return -2; //数据被截断
    }
    
    retun 0; //正常拷贝
}

5.2 测试验证

// gcc my_strlcpy.c -o my_strlcpy
#include <stdio.h>
#include <string.h>

int my_strlcpy(char *dst, const char *src, size_t dstSize) 
{
    size_t srcLen = strlen(src);
    
    if (NULL == dst || NULL == src || 0 == dstSize)
    {
        return -1; //参数错误
    }
    
    size_t maxCopyLen = dstSize - 1;
    strncpy(dst, src, maxCopyLen);
    dst[maxCopyLen] = '\0';
    
    if (srcLen > maxCopyLen)
    {
        printf("%zu > %zu, copy oversize! only copy:%s\n", srcLen, maxCopyLen, dst);
        return -2; //数据被截断
    }
    
    return 0; //正常拷贝
}

int main() 
{
    char src[] = "Hello, World";
    char dest[10] = {0}; 
    char other[10] = {0};

    int ret = my_strlcpy(dest, src, sizeof(dest));
    if (ret)
    {
        printf("err! my_strlcpy ret:%d\n", ret);
    }
    printf("复制后的字符串: %s\n", dest);
    
    printf("dest addr:%p\n", dest);
    for (int i=0; i<sizeof(dest); i++)
    {
        printf("dest[%d]:%x(%c)\n", i, dest[i], dest[i]);
    }
    
    printf("other addr:%p\n", other);
    size_t bytes = (char*)other - (char*)dest;
    printf("other (other-dest):%zu\n", bytes);
    for (int i=0; i<sizeof(other); i++)
    {
        printf("other[%d]:%x(%c)\n", i, other[i], other[i]);
    }

    return 0;
 }

运行结果:

6 总结

本篇介绍了C语言中如何安全的进行字符串拷贝,首先测试了在使用strcpy、strncpy、strlcpy进行字符串拷贝时可能遇到的问题,然后对比这两种方式的基础差别,最后通过自定义封装strncpy来实现安全拷贝字符串的功能。

以上就是C语言安全的进行字符串拷贝的几种方法的详细内容,更多关于C语言进行字符串拷贝的资料请关注脚本之家其它相关文章!

相关文章

  • C++设计模式之迭代器模式

    C++设计模式之迭代器模式

    这篇文章主要介绍了C++设计模式之迭代器模式,本文讲解了什么是迭代器模式、迭代器模式的代码实例等内容,需要的朋友可以参考下
    2014-10-10
  • Qt实现验证码相关功能的代码示例

    Qt实现验证码相关功能的代码示例

    验证码的原理基于人类视觉和计算机视觉的差异性,通过给用户显示一些难以被机器识别的图形或文字,让用户进行人机交互,确认自己的身份,这样可以有效保护网站安全,所以本给大家介绍了Qt实现验证码相关功能的代码示例,感兴趣的朋友可以参考下
    2024-01-01
  • C程序结构的入门

    C程序结构的入门

    在我们学习 C 语言的基本构建块之前,让我们先来看看一个最小的 C 程序结构,在接下来的章节中可以以此作为参考
    2021-06-06
  • C语言数组和指针,内存之间的关系

    C语言数组和指针,内存之间的关系

    这篇文章主要介绍了C语言数组和指针,内存之间的关系,首先论证一维数组和一级指针之前的关系,我们常常使用一级指针指针的方式访问一维数组,只有对内存的理解到位才能理解它们直接的关系。需要的小伙伴可以参考一下
    2022-02-02
  • C++链表实现通讯录设计

    C++链表实现通讯录设计

    这篇文章主要为大家详细介绍了C++链表实现通讯录设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • C++和java设计模式之单例模式

    C++和java设计模式之单例模式

    这篇文章主要为大家详细介绍了C++和java设计模式之单例模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • C++ vector从入门到模拟实现过程

    C++ vector从入门到模拟实现过程

    这段文章详细讲解了C++ STL中的vector容器,从其优势、基础删查改操作到迭代器失效等,并提供了模拟实现和常见陷阱的解析,帮助开发者全面掌握vector的用法,重点强调了reserve的用法、迭代器失效的处理和深拷贝、浅拷贝的区别,感兴趣的朋友一起看看吧
    2026-06-06
  • c++11中关于std::thread的join的详解

    c++11中关于std::thread的join的详解

    这篇文章主要介绍了c++11中关于std::thread的join详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 关于VS2019 C++项目同时出现LNK2005 和LNK1169 error 的解决办法

    关于VS2019 C++项目同时出现LNK2005 和LNK1169 error 的解决办法

    这篇文章主要介绍了关于VS2019 C++项目同时出现LNK2005 和LNK1169 error 的解决办法,本文给大家介绍的非常详细,对大家的学习工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • Matlab实现将图像序列合并为视频的方法详解

    Matlab实现将图像序列合并为视频的方法详解

    MATLAB是一种高性能语言,用于操纵矩阵、执行技术计算、绘图等。它代表矩阵实验室。借助这个软件,我们可以从图像中创建视频。这篇文章主要介绍了Matlab实现将图像序列合并为视频的四个方法,希望对大家有所帮助
    2023-03-03

最新评论