11

I am writing a C program that is expected to be compiled with all major compilers. Currently I am developing on GCC on a linux machine and will compile on MSVC before committing the code. To make the cross-compiling easy, I am compiling with -ansi and -pedantic flags. This worked well until I started using snprintf which is not available in C89 standard. GCC can compile this without the -ansi switch but MSVC will fail always as it doesn't have C99 support.

So I did something like,

#ifdef WIN32 #define snprintf sprintf_s #endif 

This works well because snprintf and sprintf_s has same signatures. I am wondering is this the correct approach?

4
  • 1
    isn't snprintf standard for all C in any platform? Commented Oct 20, 2010 at 9:20
  • 2
    no. snprintf is part of C99 standard. MSVC doesn't have a C99 implementation. Commented Oct 20, 2010 at 9:21
  • 6
    sprintf_s is not equivalent. snprintf returns the number of characters that would have been written, while sprintf_s returns -1 on truncation. See this discussion. Commented Oct 20, 2010 at 9:23
  • See answer of similar post link Commented Jan 3, 2012 at 13:33

5 Answers 5

17

I found this on using _snprintf() as an alternative, and the gotchas involved if the buffer overrun protection actually triggers. From what I could see at a quick glance, similar caveats apply to sprintf_s.

Can you see the problem? In the Linux version, the output is always null-terminated. In MSVC, it's not.

Even more subtle is the difference between the size parameter in Linux and count parameter in MSVC. The former is the size of the output buffer including the terminating null and the latter is the maximum count of characters to store, which excludes the terminating null.

Oh, and don't forget to send a mail to Microsoft demanding they support current language standards. (I know they already announced they have no plan to support C99, but bugger them anyway. They deserve it.)

Bottom line, if you want to play it really safe, you'll have to provide your own snprintf() (a wrapper around _snprintf() or sprintf_s() catching their non-standard behaviour) for MSVC.

Sign up to request clarification or add additional context in comments.

4 Comments

I suggest a "contract wrapper" around existing implementations (as mentioned in the answer) instead of dragging any third-party code into your project (as suggested by pmg). A complete *printf() implementation is quite large.
Actually a complete printf implementation can be very small. Normally I would agree with your principle of wrapping broken implementations rather than reimplementing them, but since Windows' *printf also has some broken things you can't easily wrap away (like backwards interpretation of %s and %ls in the wide variants), I wonder if just replacing it might be the best approach.
Actually another thing you can fix at the same time is MS's inexact floating point printing.
A naive *printf() implementation can be very small. A complete one can become quite something. My own implementation (admittedly written in a not-very-terse style) has a bit over 500 lines already, without support for %e, %f, %g, wide chars, or multibyte format strings.
15

Your proposal can work if you are being careful. The problem is that both function behave slightly different, if that is not a problem for you, you are good to go, otherwise think about a wrapper function:

Differences between MSVCs _snprintf and official C99 (gcc,clang) snprintf:

Return value:

  • MSVC: return -1 if buffer size not enough to write everything (not including terminating null!)
  • GCC: return number of characters that would have been written if buffer large enough

Written bytes:

  • MSVC: write as much as possible, do not write NULL at end if no space left
  • GCC: write as much as possible, always write terminating NULL (exception: buffer_size=0)

Interesting %n subtlety: If you use %n in your code, MSVC will leave it unitialized! if it it stops parsing because buffer size is to small, GCC will always write number of bytes which would have been written if buffer would have been large enough.

So my proposal would be to write your own wrapper function mysnprintf using vsnprintf / _vsnprintf which gives same return values and writes the same bytes on both platforms (be careful: %n is more difficult to fix).

1 Comment

It's a little easier to wrap at the next level up. I had to do this a while ago, and the approach I used had an inner function which would alloca() a buffer of a specified size, and call vsnprintf; if the result fit, then it was disposed of to other storage, and the function would return OK. If not, the function would return a 'try again with specified larger buffer size' code. For gcc the 'try-again' size was known from the vsnprintf return, and for MSVC it would just keep increasing based on some heuristics.
1

You could open the NUL special file for MSVC and write to that. It will always tell you how many bytes are needed, and won't write to anything. Like so:

int main (int argc, char* argv[]) { FILE* outfile = fopen("nul", "wb"); int written; if(outfile == NULL) { fputs ("could not open 'nul'", stderr); } else { written = fprintf(outfile, "redirect to /dev/null"); fclose(outfile); fprintf(stdout, "didn't write %d characters", written); } return 0; } 

You then should know how many bytes to allocate to use sprintf sucessfully.

Comments

0

the most complete answer (you can improve if you wish), put that into a sticker

#if __PLATFORM_WIN_ZERO_STANDARD__ static inline int LIBSYS_SNPRINTF(char * str, size_t size, const char * format, ...) { int retval; va_list ap; va_start(ap, format); retval = _vsnprintf(str, size, format, ap); va_end(ap); return retval; } static inline int LIBSYS_VASPRINTF(char **ret, char * format, va_list ap) { int wanted = vsnprintf(*ret = NULL, 0, format, ap); if((wanted > 0) && ((*ret = LIBSYS_MALLOC(1 + wanted)) != NULL)) { return vsprintf(*ret, format, ap); } return wanted; } static inline int LIBSYS_ASPRINTF(char **ret, char * format, ...) { int retval; va_list ap; va_start(ap, format); retval = LIBSYS_VASPRINTF(ret, format, ap); va_end(ap); return retval; } #else #define LIBSYS_SNPRINTF snprintf #define LIBSYS_VASPRINTF vasprintf #define LIBSYS_ASPRINTF asprintf #endif 

2 Comments

Pretty sure inline and variadic functions are C99 features.
@Thomas printf() is a variadic function. It has been around for a while ... So no, that's not a C99 addition.
-10

No. Your approach is doomed to failure.

sqrt and cos have the same prototype. Do you think you can swap them in a program and obtain the same behaviour from before / after the change?


You probably should write your own snprintf, or download an implementation from the internet (google is your friend) and use that both in Linux and Windows.

1 Comment

This answer is not useful. The question put in a different way: Since snprintf and sprintf_s have the same signature, do they also have the same functionality ? Your answer basically says: I don't know.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.