目录
strcpy
strncpy
memset
前言:因为C语言不能像C++的string那样重载=、+等运算符,所以C语言提供了用于字符串拷贝的函数strcpy与strncpy,但是这两个函数都存在着一些问题;
我们需要先知道,字符串的结束标志是\0,如果没有\0不能称为字符串;
strcpy
我们先看strcpy函数:
char* strcpy(char* dest, const char* src);
注意使用strcpy需要在第一行加上#define _CRT_SECURE_NO_WARNINGS一定要是第一行,其他地方不行;
然后输出一下字符串:
没有问题,我们单个输出name数组的每个成员的ascii码值试一下:
运行:
我们可以看到,因为我们并没有初始化,所以strcpy函数是帮我们把hello后的第六个元素置为0了,因为有\0才是字符串嘛;
如果我们的name只有五个元素,但是我们依旧赋值给他hello,那么还会不会补充\0呢?
如下:
程序直接崩掉了;
所以我们如果给字符串使用strcpy的时候一定不要忘了给\0留个位置;
可能大家会问,为什么C语言的库函数strcpy没有考虑到内存越界的问题呢?这个在文章的最后我会告诉大家;
strncpy
我们再来看看strncpy,这个函数的声明是这样的:
char* strncpy(char* dest, const char* src, const size_t n);
就是比strncpy多了一个参数,用于指定拷贝多少内容;
先看代码,这是第一种拷贝方式,第三个参数比第二个参数要长的时候:
运行一下:
我们直接cout输出一下:
没有任何异常;
下面进行第二种拷贝方式:
运行:
我们再用cout<<输出一下整个字符串,想必大家已经猜到结果了:
因为内存越界,读取到了一堆垃圾值,cout<<输出直到遇到了\0才停下来;
可能有人会问:那是不是第一种拷贝场景比第二种常见很多?
实际上并不是的。
那么怎么样尽量避免这种问题,让strncpy安全一点呢?
因为在实际开发中,我们很多时候不知道src字符串的长度,只知道dest字符串的长度,所以我们的第三个参数一般都是填dest字符串长度-1;
就比如上面,如果我们的name长为11,那么第三个参数就填10;这样的话如果src比10短,那么strncpy会自动填充几个\0,如果src比10长,那么他也只能拷贝10个元素,我们在结尾将name[10](最后一个元素)手动赋值为\0不就安全了嘛;也避免了内存越界的问题;
memset
其实在实际开发中,还有一种常用的方式:memset();
void *memset(void *str, int c, size_t n)
第一个参数就是待初始化内存块的首地址;
第二个参数是将这块地址中的值初始化为什么?
第三个参数就是初始化内存块的大小;
我们使用这个函数将name的元素全部初始化为0:
运行一下:
可以看到,除了我们指定的那三个元素外,其他的都被memset置0了;这样如果输出的话,遇到\0就会结束,所以肯定没问题,我们输出一下:
这样也就成功解决了;
为什么str(n)cpy库函数没有判断内存可能会越界的问题?
因为不论是strcpy还是strncpy他们的参数都是字符串的起始地址,没有让传入目标字符串的长度,所以他们根本无法判断你的dest字符串到底有多长,也就没有办法判断内存越界问题了;
不过我们可以自己做一个比较安全的strcpy函数,加上目标字符串长度参数,或者用其他方式让内存不能越界,感兴趣的小伙伴可以自己尝试做一下,当然网上也有很多案例,可以借鉴,为自己的框架如虎添翼;
注意事项
1、字符串在每次使用之前都要初始化,初始化最好的方法就是memset,减少入坑的可能,注意是每次,不是第一次;
2、使用strncpy的时候,尽量把第三个参数写成sizeof(目标字符串)-1,也能避免很多入坑的可能;
3、在VS中,如果要使用C标准字符串操作函数,要在源代码文件的最上面加#define _CRT_SECURE_NO_WARNINGS,当然VS还提供了一些函数可以解决字符串安全问题,比如strcpy_s,strcat_s,但是不建议使用,因为VS有,但是Linux没有,兼容性低;