Using snprintf in a cross-platform application

2019-02-16 18:04发布

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?

5条回答
唯我独甜
2楼-- · 2019-02-16 18:25

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.

查看更多
smile是对你的礼貌
3楼-- · 2019-02-16 18:34

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).

查看更多
干净又极端
4楼-- · 2019-02-16 18:40

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.

查看更多
在下西门庆
5楼-- · 2019-02-16 18:40

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.

查看更多
Bombasti
6楼-- · 2019-02-16 18:43

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
查看更多
登录 后发表回答