Нужна #define для версий Visual Studio, которые включают безопасные строковые функции (чтобы избежать _CRT_SECURE_NO_DEPRECATE)

В то время как я пытался использовать 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, но ничего полезного не нашел.

Ответ 1

Я нашел решение; _MSC_VER macro/define делает это простым. Поскольку защищенные строковые функции были добавлены в Visual Studio 2005 (версия VС++ 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 одновременно (по крайней мере, в некоторой степени).

Ответ 2

Некоторые из защищенных функций Microsoft являются частью С++ 11, поэтому теперь они должны быть переносимыми. Важным отличием между безопасными функциями и традиционными является поведение исключений, а иногда и возвращаемые значения. Многие программисты тоже не обращают внимания; эти различия часто игнорируются. Например, поведение исключения snprintf отличается от _snprintf_s:

snprintf возвращает количество символов, необходимых для печати строки, а не                     считая завершающий нулевой символ, независимо от размера буфера.                      Я не думаю, что snprintf сам вызывает исключения, но недопустимый доступ к памяти                     будет.

_snprintf_s возвращает то же значение, что и snprintf, если buff достаточно велико, но                      если бафф слишком мал, или buff или fmt является указателем NULL, _snprintf_s вызывает                      недопустимый обработчик параметров, устанавливает errno = ERANGE или EINVAL, соответственно,                      и возвращает -1.   Если поведение исключения является важным в вашем устаревшем коде, обратите внимание, когда вы переходите от старых традиционных функций к защищенным версиям.

Я боролся с защищенными Microsoft функциями "_s" в течение нескольких лет, особенно при написании кода, который был скомпилирован для платформ Windows в Visual Studio и для "nix-платформ" с использованием gcc/g++. Это также было болью при повторном использовании старого исходного кода, потому что это тяжелая работа, чтобы пройти через код, изменяющий fprintf() на fprintf_s() и т.д. Макрос _CRT_SECURE_NO_DEPRICAT подавляет предупреждения об устаревании, но я никогда не был поклонником закрытия компилятора предупреждения без устранения основных проблем; предупреждения выдаются по какой-либо причине.

Мой временный патч (который я буду признавать, что я все еще использую время от времени) был включенным файлом с макросами и несколькими встроенными функциями для сопоставления традиционных и защищенных функций. Конечно, сопоставления не имитируют поведения исключений и возвращаемые значения. Если вы хотите, вы можете написать функции, чтобы имитировать это также, но в какой-то момент просто будет проще использовать защищенные функции и изменить свой старый код, чтобы сделать то же самое. Кроме того, некоторые из защищенных функций не могут быть легко отображены, например. sprinf to sprintf_s.

Здесь мой файл include (больше комментариев, чем код, но стоит их прочитать IMHO):

#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 were it 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 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