A while back I tried to use Visual Studio 2010 to compile an MFC program that used a library I had written in Visual Studio 2003. Not surprisingly, I got a bunch of warnings about deprecation and using the secure versions of various string functions.
I then updated the relevant functions in the library to use the secure functions and it compiled fine.
I later tried to compile it again on the other system with Visual Studio 2003 and got nagged about the secure functions not existing.
I decided to create a hybrid approach that would allow me to compile programs that use the library in either environment, making use of the secure functions if available, and if not, aliasing them to the old ones.
At first I considered checking each function to see if a secure version exists, but that won’t work and requires separate work for each and every function:
#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
…
So what I’m trying to figure out is a way to determine if the secure functions exist. I know that they were introduced in Visual Studio 2005, but is there a #define
or something that can be used as follows?
#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
I checked crtdefs.h
but found nothing useful.
I found a solution; the _MSC_VER
macro/define makes this simple. Since the secure string functions were added in Visual Studio 2005 (VC++ version 1400
, then it is sufficient to do something like this:
#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
Now when the code is compiled under VS2005+, it will have the added security, and when compiled on VS2003-, it will still compile without modification, albeit without the extra security.
This makes porting and updating easier because you can update the library functions and use the secure string functions in the code even if you can’t compile them with VS2005+ just yet. This way when you do upgrade the compiler, you won’t have to make any changes to the library or the code to reap the benefits. It also makes it easier to work on the same code-base on older and newer versions of Visual Studio concurrently (at least to some degree).
Some of Microsoft's secure functions are part of C++11, so they should be portable now.
An important difference between the secure functions and the traditional ones are the exception behaviors, and sometimes the return values. Many programmers don't pay attention to either; those differences are often overlooked.
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 itself raises exceptions, but an invalid memory access
would.
_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 exception behavior is important in your legacy code, pay attention when you convert from the old traditional functions to the secure versions.
I struggled with Microsoft's secure "_s" functions for a few years, especially when writing code that was compiled for Windows platforms in Visual Studio and for 'nix platforms using gcc/g++. It was also an pain when reusing old source code because it's a chore to go through the code changing fprintf() to fprintf_s(), etc. The _CRT_SECURE_NO_DEPRICAT macro suppresses the deprecation warnings, but I've never been a fan of shutting up compiler warnings without fixing the underlying problems; the warnings are issued for a reason.
My temporary patch (which I'll admit I still use from time to time) was an include file full of macros and a few inline functions to map the traditional and secure functions. Of course the mappings don't mimic the exception behaviors and return values. If you want, you could write the functions to mimic that also, but at some point it's just going to be easier to use the secure functions and change your old code to do the same. Also, some of the secure functions cannot be readily mapped, e.g. sprinf to sprintf_s.
Here's my include file (more comments than code, but worth reading them 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'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