c99-compat.msvcrt.h: New compatibility header for some MSVCRT functions.
authorDavid Korth <gerbilsoft@gerbilsoft.com>
Sat, 12 Sep 2015 16:45:20 +0000 (12:45 -0400)
committerDavid Korth <gerbilsoft@gerbilsoft.com>
Sat, 12 Sep 2015 16:45:20 +0000 (12:45 -0400)
This is specifically for snprintf() and vsnprintf(). Other C99 functions
that have different names in MSVC are handled by MinGW-w64's headers
with no overhead, so those remain in c++11-compat.msvc.h.

The specific issue is how _snprintf() handles truncation and NULL termination.
If the string fits the buffer exactly, _snprintf() will return size, but the
buffer will NOT be NULL-terminated. If the string is over the size of the
buffer, _snprintf() will return -1, and the buffer may or may not be blank.
(MSVCRT 2010 blanks the buffer by setting str[0] = 0; not sure about the
system MSVCRT.DLL, so I'm clearing it manually in the inline function.)

If using MinGW-w64 and __USE_MINGW_ANSI_STDIO is defined, MinGW-w64 will
provide its own snprintf() and vsnprintf() functions that are C99 compatible.
I'm not defining this, since those functions take up extra space, and we
don't strictly need full C99 compatibility.

src/c++11-compat.h
src/c++11-compat.msvc.h
src/c99-compat.msvcrt.h [new file with mode: 0644]

index e94e9ac..1a1d576 100644 (file)
@@ -92,4 +92,12 @@ namespace std {
 # endif
 #endif
 
+/**
+ * MSVCRT prior to MSVC 2015 has a non-compliant _snprintf().
+ * Note that MinGW-w64 uses MSVCRT.
+ */
+#ifdef _WIN32
+#include "c99-compat.msvcrt.h"
+#endif
+
 #endif /* __CXX11_COMPAT_H__ */
index 61ca2cd..c5483b8 100644 (file)
  *
  * Note that later versions of MSVC (esp. 2013 and 2015)
  * have added more C99 functionality, since C99 is included
+ * in C++ 2011.
  */
 
-/** snprintf(), vsnprintf() **/
-
-/**
- * MSVC 2015 (14.0) supports snprintf() and vsnprintf().
- * Older versions have them prefixed with underscores.
- */
-#if _MSC_VER < 1400
-/**
- * MSVC 2005 added support for variadic macros. *
- * https://msdn.microsoft.com/en-US/library/ms177415(v=vs.80).aspx
- * TODO: Verify MSVC 2002 and 2003.
- */
-#define snprintf _snprintf
-#define vsnprintf _vsnprintf
-#elif _MSC_VER < 1900
-/* MSVC 2005 - 2013: variadic macros supported, but no snprintf(). */
-#define snprintf(str, size, format, ...) _snprintf(str, size, format, __VA_ARGS__)
-#define vsnprintf(str, size, format, ap) _vsnprintf(str, size, format, ap)
-#endif /* _MSC_VER < 1900 */
-
 /** strcasecmp(), strncasecmp() **/
 
 /**
diff --git a/src/c99-compat.msvcrt.h b/src/c99-compat.msvcrt.h
new file mode 100644 (file)
index 0000000..b54f990
--- /dev/null
@@ -0,0 +1,95 @@
+#ifndef __C99_COMPAT_MSVCRT_H__
+#define __C99_COMPAT_MSVCRT_H__
+
+#ifndef _WIN32
+#error c99-compat.msvcrt.h should only be included in Win32 builds.
+#endif
+
+#if defined(__GNUC__)
+
+// MinGW-w64.
+// TODO: Check standard MinGW?
+#if !defined(__USE_MINGW_ANSI_STDIO)
+// Using MSVCRT's snprintf().
+#define ENABLE_C99_SNPRINTF_WRAPPERS 1
+#define snprintf(str, size, format, ...)  C99_snprintf(str, size, format, __VA_ARGS__)
+#define vsnprintf(str, size, format, ...) C99_vsnprintf(str, size, format, __VA_ARGS__)
+#endif
+
+#elif defined(_MSC_VER)
+
+/**
+ * MSVC 2015 added proper support for C99 snprintf().
+ * Older versions have _snprintf(), which isn't fully compatible.
+ */
+
+#if _MSC_VER < 1400
+/**
+ * MSVC 2005 added support for variadic macros.
+ * https://msdn.microsoft.com/en-US/library/ms177415(v=vs.80).aspx
+ * TODO: Verify MSVC 2002 and 2003.
+ */
+#define ENABLE_C99_SNPRINTF_WRAPPERS 1
+#define snprintf  C99_snprintf
+#define vsnprintf C99_vsnprintf
+#elif _MSC_VER < 1900
+/** MSVC 2005-2013: variadic macros are supported, but still no snprintf(). */
+#define ENABLE_C99_SNPRINTF_WRAPPERS 1
+#define snprintf(str, size, format, ...)  C99_snprintf(str, size, format, __VA_ARGS__)
+#define vsnprintf(str, size, format, ...) C99_vsnprintf(str, size, format, __VA_ARGS__)
+#endif
+
+#endif
+
+#ifdef ENABLE_C99_SNPRINTF_WRAPPERS
+#include <stdarg.h>
+#include <stdio.h>
+
+static
+#if defined(_MSC_VER)
+__forceinline
+#elif defined(__GNUC__)
+__inline
+__attribute__ ((__format__ (gnu_printf, 3, 0)))
+__attribute__ ((__nonnull__ (3)))
+__attribute__ ((always_inline))
+__attribute__ ((unused))
+#endif
+int C99_vsnprintf(char *str, size_t size, const char *format, va_list ap)
+{
+       int ret = _vsnprintf(str, size, format, ap);
+       if (ret >= (int)size) {
+               // Make sure the buffer is NULL-terminated.
+               str[size-1] = 0;
+       } else if (ret < 0) {
+               // Make sure the buffer is empty.
+               // MSVCRT *should* do this, but just in case...
+               str[0] = 0;
+       }
+       return ret;
+}
+
+static
+#if defined(_MSC_VER)
+__forceinline
+#elif defined(__GNUC__)
+__inline
+__attribute__ ((__format__ (gnu_printf, 3, 0)))
+__attribute__ ((__nonnull__ (3)))
+/* NOTE: gcc complains that this function cannot be inlined
+ * becuase it uses variable argument lists.
+__attribute__ ((always_inline))*/
+__attribute__ ((unused))
+#endif
+int C99_snprintf(char *str, size_t size, const char *format, ...)
+{
+       int ret;
+       va_list ap;
+       va_start(ap, format);
+       ret = C99_vsnprintf(str, size, format, ap);
+       va_end(ap);
+       return ret;
+}
+#endif /* ENABLE_C99_SNPRINTF_WRAPPERS */
+
+#endif /* __C99_COMPAT_MSVCRT_H__ */