【那些年C++趟过的坑】strncpy字符串截断缺陷


前言

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

字符串拷贝strcpy,学过C/C++的人都非常熟悉,但这个函数有一个需要注意的地方,当字符串src大小比目标数组dest大时,那就会内存越界,发生崩溃。为了解决这个问题,strncpy诞生了,先看下定义:

char * strncpy ( char * dest, const char * src, size_t num )

strncpy加一个参数num, 意为最大可拷贝的空间大小,就是说当字符串src长度比dest大时,会自动截断。
不过谁知道呢,strncpy会不会在截断发生时被’\0’呢。

小贴士: C语言字符串末尾是以’\0’结尾的,如:
char p[] = “Hello World!”; 实际上这实际存储空间比字符串“Hello World!”大一个,因为最后有一个’\0’。

于是有一段时间,为了毫无顾忌的使用strncpy,有了下面丑陋的宏定义

#define my_strncpy(dst, src) strncpy(dst, src, sizeof(dst)-1 >= strlen(src) ? strlen(src) : sizeof(dst)-1)

代码解释:

sizeof(dst)是目标数组的大小,sizeof(dst)-1是为了发生截断是留下一个’\0’的位置,strlen(src)是待拷贝字符串的长度。目标数组大小减1大于等于待拷贝字符串长度时,也就不会发生截断,则num=strlen(src); 反之,发生截断时,则num=sizeof(dst)-1。
这样做看似,保证了发生截断时,预留一下一个‘\0’位置, 但真的靠谱吗?
(先说结论:不靠谱,以免一些急于寻找结果的,没往下看,直接采用上面宏定义代码)
如果对探究过程不感兴趣,只想知道结果,可以直接跳到1.5章节看结论,那里会对strncpy缺陷作一个总结 ==>传送门


一、探究strncp字符拷贝截断缺陷

通过前言部分,我们知道了,strcpy不可靠,可能发生数组越界引发崩溃,而strncpy貌似也用着不踏实。所以这一节我们做几个实验看看strcpy在各种情况下的表现。

char * strncpy ( char * dest, const char * src, size_t num )

下面通过四个小实验来探究下strncpy是不有缺陷:
未截断时:
1、dst长度正好等于src长度+1 (strncpy是否会自动补’\0’ )
2、dst长度远大于src长度(strncpy是否会操作长度超出部分的内存)
截断时:
3、dst长度-1比src长度小(截断时是否会补’\0’)
4、num长度是dst长度-1 (故意预留一个位置,看看是否会补‘\0’)

1.1 dst长度等于src长度 + 1 (未截断)

char * strncpy ( char * dest, const char * src, size_t num )

这种情况下,dst正好能盛放src,并且后面应该会补一个’\0’。
代码如下:

#include <stdio.h>
#include <memory.h>
int main()
{
        char szTemp[13];
        memset(szTemp, 1, sizeof(szTemp));
        strncpy(szTemp, "Hello World!", sizeof(szTemp));
        for (int i = 0; i < sizeof(szTemp)-1; i++)
        {
                printf("[%c] ", szTemp[i]);
        }
        printf("[%d]", szTemp[sizeof(szTemp)-1]);
        printf("\n");
        getchar();
}

代码解释:

字符串“Hello World!” 长度为12, 而szTemp数组大小是13,正好可以盛放字符串“Hello World!”并预留了’\0’的位置; 第6行,memset把数组szTemp全部元素置为1,是为了验证,拷贝完成后,是不是真的会补’\0’。第8-12行打印szTemp所有元素的值。预期输出结果应该是szTemp最后一个元素是‘\0’,也就是0。

输出结果:
在这里插入图片描述
可以看到最一个元素是0,和预期相符。说明在不会发生截断时,strncp会在字符串末尾补’\0’。


1.2 dst大小远大于src长度时(未截断)

char * strncpy ( char * dest, const char * src, size_t num )

因为1.1章节知道了,在不发生截断时strncpy会字符串末尾补’\0’,更进一步我想知道的是,当dst大小远大于src长度时,除了补’\0’, 其他元素会不会操作呢。
实验代码:

#include <stdio.h>
#include <memory.h>
int main()
{
        char szTemp[20];
        memset(szTemp, 1, sizeof(szTemp));
        strncpy(szTemp, "Hello World!", sizeof(szTemp));
        for (int i = 0; i < strlen("Hello World!"); i++)
        {
                printf("[%c] ", szTemp[i]);
        }
       int len = strlen("Hello World!");
       for (int i = 0; i < sizeof(szTemp) - strlen("Hello World!"); i++)
        {
                printf("[%c] ", szTemp[i+len]);
        }
        printf("\n");
        getchar();
}

代码解释:

目标数组szTemp长度是20,比字符串“Hello World!”长度要大的多。第7行strncp中num参数传的是sizeof(szTemp),也就是szTemp的大小,就是要看看除了补’\0’,还会做什么。第8-16行是打印szTemp的元素,字符串部分采用%c字符形式,其他部分采用%d整型,因为其他部分可能不是字符。

结果输出:
在这里插入图片描述
啧啧啧,不看不知道,一看吓一跳,这已经不是补’\0’,而是所后面元素全置成0了。示意如下:
在这里插入图片描述

1.3 dst大小小于src长度(截断)

char * strncpy ( char * dest, const char * src, size_t num )

有了1.2章节的实验结果, 这次不再实验临界状态,直接让dst大小小于src长度,来看看补\0的情况。
代码示例:

#include <stdio.h>
#include <memory.h>
int main()
{
        char szTemp[11];
        memset(szTemp, 1, sizeof(szTemp));
        strncpy(szTemp, "Hello World!", sizeof(szTemp));
        for (int i = 0; i < sizeof(szTemp)-1; i++)
        {
                printf("[%c] ", szTemp[i]);
        }
        printf("[%d]", szTemp[sizeof(szTemp)-1]);
        printf("\n");
        getchar();
}

代码解释:

szTemp大小是11,小于字符串“Hello World!”长度12。如果strncpy在截断时补’\0’的话,则最后一个元素会是0。

输出结果:
在这里插入图片描述
从结果上看,最后一个元素不是0是100, 而100是d的ascii码值。所以,当截断发生时,strncpy直接把src里的前num个元素拷贝到目标数组dst里。
这真是不幸的消息, strncpy确实不靠谱!

1.4 num大小等dst大小减1(截断)

char * strncpy ( char * dest, const char * src, size_t num )

书接1.3, 截断时strncpy不会补0,但如果拷贝的时候给数组dst的长度预留一个元素呢,会不会strycpy就可以补0呢,下面实验一下:
代码示例:

#include <stdio.h>
#include <memory.h>
int main()
{
        char szTemp[11];
        memset(szTemp, 1, sizeof(szTemp));
        strncpy(szTemp, "Hello World!", sizeof(szTemp)-1);
        for (int i = 0; i < sizeof(szTemp)-1; i++)
        {
                printf("[%c] ", szTemp[i]);
        }
        printf("[%d]", szTemp[sizeof(szTemp)-1]);
        printf("\n");
        getchar();
}

代码解释:

本次实验和1.3只有第7行不同,num=sizeof(szTemp)-1, 期望是留出一个位置自动补’\0’。

输出结果:
在这里插入图片描述
从结果上看,没有补’\0’,与预期不一样。通过1.3、1.4的实验可以得出,strncpy在截断的时候不会自动在字符串末尾补'\0'

1.5 结论

通过以上4个实验,可以证明以下两点:

  1. strncpy在不发生截断时,将除待拷贝字符串部分全部置为0
  2. 当发生截断时,strncpy不会自动补'\0', 只是复制待拷贝字符串src的前num个元素

本来是觉得不靠谱,那现在是真不靠谱了,还得用本文最开始的宏定义,不过得改进一下:

原始版:

#define my_strncpy(dst, src) strncpy(dst, src, sizeof(dst)-1 >= strlen(src) ? strlen(src) : sizeof(dst)-1)

改进版:

#define my_strncpy(dst, src, dst_size) strncpy(dst, src, dst_size); \
			if (dst_size <= strlen(src)) dst[dst_size-1] = '\0'

改进版代码解释:

首先是dst_size是目标数组dst的大小, 第2行当dst长度小于等于字符串长度时, 手动在目标数组最后一位设置为’\0’。这个宏定义比原始版的多了补’\0’ 的操作,可以放心使用了~
另外,既然strncp如此难用,作为C++开发者完全可以自己实现一个,当然也可以找其他替换函数,这里不再过多介绍。

总结

本文通过几个实验介绍了strncpy截断处理不会补\0的缺陷,给出了一个简单的改进代码;如果觉得有帮助,内容还可以,可以给博主点个关注。另外,如果文中有错误不当之处,希望不吝指正,大家一起共同进步。

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一马途追

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值