前言
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个实验,可以证明以下两点:
strncpy在不发生截断时,将除待拷贝字符串部分全部置为0
;当发生截断时,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的缺陷,给出了一个简单的改进代码;如果觉得有帮助,内容还可以,可以给博主点个关注。另外,如果文中有错误不当之处,希望不吝指正,大家一起共同进步。
转载:https://blog.csdn.net/xxm524/article/details/125692920