【问题标题】:Need a #define for Visual Studio Versions that Include Secure String Functions (to Avoid _CRT_SECURE_NO_DEPRECATE)需要包含安全字符串函数的 Visual Studio 版本的#define(以避免 _CRT_SECURE_NO_DEPRECATE)
【发布时间】:2011-09-08 05:54:46
【问题描述】:

不久前,我尝试使用 Visual Studio 2010 编译一个 MFC 程序,该程序使用了我在 Visual Studio 2003 中编写的库。毫不奇怪,我收到了一堆关于弃用和使用各种字符串函数的安全版本的警告.

然后我更新了库中的相关函数以使用安全函数,并且编译正常。

后来我尝试在另一个系统上用 Visual Studio 2003 再次编译它,并被抱怨不存在安全功能。


我决定创建一种混合方法,允许我编译在任一环境中使用该库的程序,如果可用,则使用安全函数,如果没有,则将它们别名为旧函数。

起初我考虑检查每个函数以查看是否存在安全版本,但这不起作用并且需要为每个函数单独工作:

#ifndef strcpy_s
    #define strcpy_s(a,b,c) strcpy(a,c)
#endif

#ifndef strcat_s
    #define strcat_s(a,b,c) strcat(a,c)
#endif

…

所以我想找出一种方法来确定是否存在安全功能。我知道它们是在 Visual Studio 2005 中引入的,但是有没有#define 或者可以使用如下的东西?

#ifndef SECURE_FUNCTIONS  // or #ifdef VS_VER_2005, #if (VS_VER >= 0x2005) etc.
    #define strcpy_s(a,b,c) strcpy(a,c)
    #define strcat_s(a,b,c) strcat(a,c)
    …
#endif

我检查了crtdefs.h,但没有发现任何有用的东西。

【问题讨论】:

    标签: visual-studio security preprocessor


    【解决方案1】:

    Microsoft 的一些安全功能是 C++11 的一部分,因此它们现在应该是可移植的。 安全函数和传统函数之间的一个重要区别是异常行为,有时还有返回值。许多程序员都不注意这两者;这些差异往往被忽视。 例如snprintf的异常行为与_snprintf_s不同:

    snprintf 返回打印字符串所需的字符数,而不是 计算终止的空字符,而不管缓冲区的大小。 我不认为 snprintf 本身会引发异常,而是无效的内存访问 会的。

    _snprintf_s 如果 buff 足够大,则返回与 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 宏抑制了弃用警告,但我从来都不喜欢关闭编译器没有解决根本问题的警告;发出警告是有原因的。

    我的临时补丁(我承认我仍然不时使用)是一个包含宏的包含文件和一些用于映射传统和安全函数的内联函数。当然,映射不会模仿异常行为和返回值。如果你愿意,你也可以编写函数来模仿它,但在某些时候,使用安全函数并更改旧代码来做同样的事情会更容易。此外,某些安全功能无法轻松映射,例如sprintf 到 sprintf_s。

    这是我的包含文件(cmets 多于代码,但值得一读,恕我直言):

    #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, strcpy, 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), (count), (source))
          #define strncat(dest, source, count)  strcat_s((dest), (count), (source))
    
       #else
          // 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.
          // 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, count, 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
    

    【讨论】:

    • 我看到@Synetech 使用了_MSC_VER &lt; 1400 作为截止点(在我的代码中它是_MSC_VER &gt;= 1400)。用他的号码(1400)代替1600;我将编辑我的解决方案以反映这一点。另外,我上面的代码中没有他的四个函数映射中的任何一个。如果需要,可以添加它们。在我有机会测试它们之前,我不会。对于传统到安全的函数映射,只能重写其中两个。
    【解决方案2】:

    我找到了解决方案; _MSC_VER 宏/定义使这变得简单。由于secure string functionsadded in Visual Studio 2005(VC++ 版本1400,那么这样做就足够了:

    #if _MSC_VER < 1400
        #define  _itoa_s(a,b,c)             _itoa(a,b,c)
        #define  wcscpy_s(a,b,c)            wcscpy(a,c)
        #define  _tprintf_s                 _tprintf
        #define  _sntprintf_s(a,b,c,d,...)  _sntprintf(a,c,d,...)
        …
    #endif
    

    现在代码在VS2005+下编译时,会增加安全性,而在VS2003-上编译时,仍然可以不加修改地编译,虽然没有额外的安全性。

    这使得移植和更新更容易,因为即使你还不能用 VS2005+ 编译它们,你也可以更新库函数并在代码中使用安全字符串函数。这样,当您升级编译器时,您无需对库或代码进行任何更改即可获得好处。它还可以更轻松地同时在旧版和新版 Visual Studio 上处理相同的代码库(至少在某种程度上)。

    【讨论】:

    • 对,_MSC_VER 的正确截止值是 1400,即 MSVC 2005。当我开始编写宏来处理不兼容的安全功能时,我正在使用 MSVC 2010 (_MSC_VER = 1600)。我怀疑我是否会再次使用 2005,但有人会。准确地说很好。
    • 谢谢。我的函数宏包含文件中没有您的四个函数中的任何一个。您是否测试了 _sntprintf_s_s 到 _sntprintf 的映射?我认为省略号在 RHS 上不起作用。
    • 另外,_sntprintf_s 到 _sntprintf 宏应该读作#define _sntprintf_s(a, b, c,...) _sntprintf((a), (c) , __VA_ARGS__)。否则,当格式后面没有参数时,您将有一个悬挂的逗号(语法错误)。我想这对于 sprintf 函数调用来说是一个不寻常的情况。 strncpy 将是可能的选择。 rhs 参数周围的额外括号是函数宏的好习惯;没有它们,总有一天你会得到意想不到的结果。根据墨菲的说法,这将是最糟糕的一天。
    猜你喜欢
    • 2021-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-04
    • 1970-01-01
    • 2021-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多