Reputation: 15511
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?
Upvotes: 11
Views: 26160
Reputation: 338
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.
Upvotes: 1
Reputation: 70333
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 andcount
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.
Upvotes: 17
Reputation: 108978
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.
Upvotes: -10
Reputation: 2422
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:
Written bytes:
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).
Upvotes: 15
Reputation: 1
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
Upvotes: 0