问题描述
||
不久前,我尝试使用Visual Studio 2010编译使用我在Visual Studio 2003中编写的库的MFC程序。毫不奇怪,我收到了一堆关于弃用和使用各种字符串函数的安全版本的警告。
然后,我更新了库中的相关功能以使用安全功能,并且编译良好。
后来,我尝试使用Visual Studio 2003在其他系统上再次对其进行编译,但对不存在的安全功能感到困惑。
我决定创建一种混合方法,该方法将允许我编译在两种环境中使用该库的程序,并在可用的情况下利用安全功能,如果没有,则将其别名为旧功能。
最初,我考虑过检查每个功能,以查看是否存在安全版本,但这无法正常工作,并且每个功能都需要单独工作:
#ifndef strcpy_s
#define strcpy_s(a,b,c) strcpy(a,c)
#endif
#ifndef strcat_s
#define strcat_s(a,c) strcat(a,c)
#endif
…
因此,我试图找出一种确定安全功能是否存在的方法。我知道它们是在Visual Studio 2005中引入的,但是是否有一个“ 1”或可以按以下方式使用的东西?
#ifndef SECURE_FUNCTIONS // or #ifdef VS_VER_2005,#if (VS_VER >= 0x2005) etc.
#define strcpy_s(a,c)
#define strcat_s(a,c)
…
#endif
我检查了crtdefs.h
,但发现没有任何用处。
解决方法
我找到了解决方案;
_MSC_VER
宏/定义使这一过程变得简单。由于安全字符串函数是在Visual Studio 2005(VC ++版本1400
)中添加的,因此只需执行以下操作:
#if _MSC_VER < 1400
#define _itoa_s(a,b,c) _itoa(a,c)
#define wcscpy_s(a,c) wcscpy(a,c)
#define _tprintf_s _tprintf
#define _sntprintf_s(a,c,d,...) _sntprintf(a,...)
…
#endif
现在,当代码在VS2005 +下进行编译时,它将具有更高的安全性;而在VS2003-上进行编译时,即使没有额外的安全性,仍然可以进行编译而无需修改。
这使移植和更新更加容易,因为即使您还不能使用VS2005 +进行编译,也可以更新库函数并在代码中使用安全字符串函数。这样,当您升级编译器时,无需对库或代码进行任何更改即可获得好处。它还使在旧版本和更新版本的Visual Studio上同时在同一代码库上进行工作变得更加容易(至少在某种程度上)。
, Microsoft的某些安全功能是C ++ 11的一部分,因此现在应该可以移植。
安全功能与传统功能之间的重要区别是异常行为,有时还有返回值。许多程序员都不关注它们。这些差异通常被忽略。
例如,snprintf的异常行为与_snprintf_s不同:
snprintf
返回打印字符串所需的字符数,而不是
无论终止缓冲区的大小如何,都要计算终止的空字符。
我不认为snprintf本身会引发异常,但会导致无效的内存访问
将。
如果buff足够大,则_snprintf_s
将返回与snprintf相同的值,但是
如果buff太小,或者buff或fmt是NULL指针,则_snprintf_s调用
无效的参数处理程序,分别设置errno = ERANGE或EINVAL,
并返回-1。
如果异常行为在旧代码中很重要,则从旧的传统函数转换为安全版本时请注意。
几年来,我一直在努力使用Microsoft \“ _ s \”的安全功能,特别是在编写为Visual Studio中的Windows平台和使用gcc / g ++的\'nix平台编译的代码时。重复使用旧的源代码时也很痛苦,因为要遍历代码将fprintf()更改为fprintf_s()等很麻烦。_CRT_SECURE_NO_DEPRICAT宏会禁止弃用警告,但我从来都不喜欢在不解决潜在问题的情况下关闭编译器警告;发出警告是有原因的。
我的临时补丁(我会不时承认,我仍然会用它)是一个包含宏的包含文件和一些内联函数,用于映射传统的安全函数。当然,映射不会模仿异常行为和返回值。如果需要,您也可以编写函数来模仿它,但是在某些时候,使用安全函数并更改旧代码以执行相同操作将变得更加容易。而且,某些安全功能不能容易地被映射,例如。 sprinf到sprintf_s。
这是我的包含文件(注释多于代码,但值得一读,恕我直言):
#pragma once
#if !defined(FCN_S_MACROS_H)
#define FCN_S_MACROS_H
///////////////////////////////////////////////////////////////////////////////
//
// These macros provide (partial) compatibility of source code developed
// for older MSVC versions and non-MSVC c++ compilers for some of Microsoft\'s
// security enhanced funcions,e.g. fscanf_s,sscanf_s,printf_s,strcpy_s,// fopen_s.... Of course the standard functions still work in MSVS,but
// the choice is either to live with the annoying warning messages (bad idea)
// or set a compiler directive to stop the warnings (bad idea--there might
// important warnings as well as the annoying ones).
//
// It looks like a lot of the secure functions are now part of C++11. Those
// functions should be used in new code. The macros below can be used for
// for as a bridge for older code,but at some point it would be best to
// upgrade the code with the more secure functions. Eventually,the depricated
// functions may be removed,but probably not for a long time.
//
// Bill Brinson
// 21 February 2011 (updated once or twice since then).
//
///////////////////////////////////////////////////////////////////////////////
// Does It Work:
//
// *** No warranty expresed nor implied. Use at your own risk. ***
//
// I\'ve tested most of the standard function to MS specific macros. They
// work in my codes so far,but Murphy says ...
//
// I usually write code in MSVS,using the standard functions,then port to
// linux if needed. I haven\'t though as much about the inverse macros,// nor have I tested all of them. They seem unnecessary anyway. Too bad: they
// tend to be simpler.
// Test the macros yourself,and investigate exception behaviors before using
// them.
//
///////////////////////////////////////////////////////////////////////////////
//
// String Functions With No Count Parameter:
//
// The string functions that don\'t specify the maximum number of bytes to copy
// into the buffer (sprintf,strcpy,...) are a problem. Using the sizeof()
// operator is a terrible idea (I should know--I though of it myself.
// Fortunately sanity prevailed before I used it in real code.
// In case you are tempted: the sizeof(buff) method WILL FAIL at runtime
// if buffer is not defined as an array (char cstring[32] or similar). For
// dynamically allocated memory,sizeof(ptr) returns the size of the pointer
// itself,not the allocated memory,so if your are copying no more than four
// bytes and you allocated at least that many,it would work due to blind luck.
// _memsize() (MS specific,but that\'s were it\'s needed) can be used for
// memory allocated with malloc,calloc,or realloc,but it doesn\'t work
// for char buff[size] or memory allocated with the new opperator. Does anyone
// still use malloc()?
// Overloaded functions taking char[] and *char to differentiate them might
// work for arrays and pointers to memory allocated by malloc et. al.,but not
// for pointers to memory allocated by new (which have the same type,so not
// differentiated by the overloaded functions).
// If someone an idea,please let me know.
//
// This should only be an issue for legacy code; use snprintf,strncpy,etc.
// in new code (which you already do,right?),and make sure count has an
// appropriate value. For legacy code containing sprintf,etc.,// I\'ve decided to just bite the bullet: let the MS compiler point out the
// unsafe functions,then change them to the safer (but standard) versions
// that specify the allowable number of bytes to copy.
//
///////////////////////////////////////////////////////////////////////////////
// Exception Behavior:
//
// This is an important difference between the MS decreed safe functions and
// the traditional C/C++ functions.
// I suspect all of the MS specific functions have different exception behaviors.
// For example the exception behavior of snprintf is different from _snprintf_s:
// snprintf returns the number of characters required to print the string,not
// counting the terminating null character,regardless of the size of the buffer.
// I don\'t think snprintf raises exceptions.
//
// _snprintf_s returns same value as snprintf if buff is sufficiently large,but
// if buff is too small,or buff or fmt is a NULL pointer,_snprintf_s invokes the
// invalid parameter handler,sets errno = ERANGE or EINVAL,respectively,// and returns -1.
// If return values and exception behaviors are important in your code,create
// your own functions to handle the conversions.
//
///////////////////////////////////////////////////////////////////////////////
// Overloads:
//
// The macros below handle only the most common (for me,at least) overloads.
//
///////////////////////////////////////////////////////////////////////////////
// Suggetions:
//
// Yes please. There are a ton of these MS specific \"safe\" functions. I\'ve
// only done a few.
//
///////////////////////////////////////////////////////////////////////////////
// License:
//
// I suppose someone might care about this.
// Sure,use what you like,delete what you don\'t. Modify it to your hearts
// content.
// I wouldn\'t mind getting an attaboy or something if it works (not required).
// If it doesn\'t work,blame MS.
//
///////////////////////////////////////////////////////////////////////////////
// #include <cstdlib> // Do I need cstdlib? Hmm...maybe for sizeof()?
#include <cstdio>
#include <string> // Need this for _stricmp
using namespace std;
// _MSC_VER = 1400 is MSVC 2005. _MSC_VER = 1600 (MSVC 2010) was the current
// value when I wrote (some of) these macros.
#if (defined(_MSC_VER) && (_MSC_VER >= 1400) )
// The function plus macro strategy could be used for most of the offending
// MS functions,particularly for maintaining consistent exception behaviors
// and return values. T
// inline is for run time efficiency,but the compiler is not
// constrained to comply.
inline extern
FILE* fcnSMacro_fopen_s(char *fname,char *mode)
{ FILE *fptr;
fopen_s(&fptr,fname,mode);
return fptr;
}
#define fopen(fname,mode) fcnSMacro_fopen_s((fname),(mode))
inline extern
char* fcnSMacro_strtok_s(char *strng,char *delimiters)
{ static char *cntx; // This static variable causes the same problem
// as the original strtok: can\'t alternate search
// strings in the same process (MS says \"thread\").
if(strng != NULL) *cntx = NULL;
char *cptr = strtok_s(strng,delimiters,&cntx);
return cptr;
}
#define strtok(strng,delim) fcnSMacro_strtok_s((strng),(delim))
#define fcloseall() _fcloseall()
// I substituded count+1 for snprintf\'s buffer size argument. For well
// written code,the buffer size should be at least one more than count
// to leave room for the terminating \'\\0\'.
#define snprintf(buff,count,...) _snprintf_s((buff),(count+1),(count),__VA_ARGS__)
#define printf(...) printf_s(__VA_ARGS__)
#define fprintf(fptr,...) fprintf_s((fptr),__VA_ARGS__)
// I don\'t have a solution for mapping sprinf to sprintf_s. There are other
// functions like this.
// #define sprintf ???
// #define strcpy(s1,s2) ???
// These mappings look trivial,but the secure functions likely have different
// exception behaviors and maybe different return values.
#define fscanf fscanf_s
#define sscanf sscanf_s
#define scanf scanf_s
// strcmpi is deprecated in VS 2015. Don\'t know about 2013 or 2014
#define strcmpi _stricmp
// No conversion needed for strncmp (yet). I guess MS hasn\'t gotten around
// to it yet.
// #define strncmp ???
#define strncpy(dest,source,count) strcpy_s((dest),(source))
#define strncat(dest,count) strcat_s((dest),(source))
#else
// I usually write code in MSVS,then port to linux if needed.
// I haven\'t though as much about the inverse macros,nor have I tested all of them.
// Test them yourself and investigate exception behaviors before using them.
#define fscanf_s fscanf
#define sscanf_s sscanf
#define scanf_s scanf
#define printf_s printf
#define sprintf_s snprintf
#define fprintf_s fprintf
#define strcpy_s(dest,source) strncpy( (dest),(source),(count) )
#define fopen_s(fp,fmt,mode) *(fp)=fopen( (fmt),(mode))
#define _fcloseall fcloseall
#define strtok_s strtok
#define _strcmpi strcmpi
#endif //_MSC_VER
#endif // FCN_S_MACROS_H