问题描述:
之前公司开发用的VS2008,后面需要将项目升级使用VS2015,重新编译都是字符串处理函数的安全检查错误,类似于:
error C4996: ‘strcpy’: This function or variable may be unsafe. Consider using strcpy_s instead
str*_s等字符串处理函数是具有更强"安全性"的CRT函数,为了安全考虑,那就替换吧;
稍微了解了一下使用方法,对于第二个参数,有说是源字符串长度+1,也有说是目的字符串buff长度,稍微写demo测试了一下,发现两种方式都可行,并且感觉作为目的字符串buff长度更合理一些,知道了目的字符串buff长度就不用担心拷贝越界了(自以为是😂);
但是测试的时候出问题,一测试就奔溃,示例代码如下:
#include <windows.h>
#include <string.h>
BYTE bszCmd[4] = { 0x01, 0x02, 0x03, 0x04 };
int main()
{
int iCmdLen = 0;
BYTE bszBuff[256] = { 0 };
strcpy_s((char*)bszBuff, 256, "hello");
iCmdLen += strlen("hello");
memcpy(bszBuff + iCmdLen, bszCmd, sizeof(bszCmd));
iCmdLen += sizeof(bszCmd);
strcat_s((char*)bszBuff, 256 - iCmdLen, "world");
iCmdLen += strlen("world");
return 0;
}
代码逻辑层没改,就是只是替换了函数strcpy->strcpy_s、strcat->strcat_s
原因分析:
经过调试发现,是strcat_s函数崩溃了,问题出在了strcpy_s上,跟strcpy_s函数的第二个参数有关系;
strcpy_s函数拷贝的字符串长度是就是第二个参数的长度,不管有没有遇到‘\0’,调试界面如下:
好吧,第二参数可以传目的字符串buff长度,但是遇到’\0’不会结束,会继续拷贝,以至于后面的内存值不可控,使用strcat_s找’\0’肯定越界;
之前的代码没问题,是因为之前strcpy拷贝到’\0’就结束,后面内存块的值不会受影响,内存值还是’\0’,使用strcat没问题;
说到底,还是自己代码不够严谨啊😰
解决方案:
首先,str*_s系列函数使用的时候,第二个参数还是尽量使用源字符串长度+1(必须大于或等于这个值,否则会断言错误),尽管使用目的字符串buff长度也可以;
另外,也要尽量避免字符串处理函数(strcpy等)和内存处理函数(memcpy等)混用;
哈哈,找那么多理由,说到底,就是代码逻辑不够严谨导致。。。
最后,附上strcpy_s源码
/***
*tcscpy_s.inl - general implementation of _tcscpy_s
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
* This file contains the general algorithm for strcpy_s and its variants.
*
****/
_FUNC_PROLOGUE
errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC)
{
_CHAR *p;
size_t available;
/* validation section */
_VALIDATE_STRING(_DEST, _SIZE); // 验证字符串是否以null结尾
_VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE); // 记录字符串的原始信息,以便拷贝失败以后恢复
p = _DEST;
available = _SIZE;
while ((*p++ = *_SRC++) != 0 && --available > 0)
{
}
// 当源字符串_SRC不到\0字符,复制长度available减到0时,会引发断言
if (available == 0)
{
_RESET_STRING(_DEST, _SIZE);
_RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
}
_FILL_STRING(_DEST, _SIZE, _SIZE - available + 1); // 完成在字符串最后加上null结束符的工作
_RETURN_NO_ERROR;
}
看完源码,我比较好奇,按道理拷贝到’\0’就结束了,为什么后面还会继续拷贝,_FILL_STRING到底做了什么