3

I am porting a win32 app to linux and instead of having bunch of #ifdefs around every _snprintf_s, I was wondering if there is a way to #define it to snprintf somehow.

So something like -

#define _snprintf_s(1,2,3,4,5) snprintf(1,2,4,5)

The third parameter, Maximum number of characters to store, or _TRUNCATE is not present for snprintf.

Is this approach right? Can I do such a #define? If so, can someone maybe point out how I should go about it?

I went through this question to know I have to be careful about such #defines.

Thanks!

Community
  • 1
  • 1
sskanitk
  • 475
  • 4
  • 11
  • 19
  • For starters, you can't use numbers as macro argument names; they have to be identifiers. – Keith Thompson Sep 14 '12 at 18:56
  • 2
    I was going to suggest using `_snprintf` for Windows and `snprintf` for Linux, but Microsoft's `_snprintf` function isn't *quite* equivalent to the standard `snprintf`. In particular, `_snprintf` can leave the target buffer without a `'\0'` terminator. – Keith Thompson Sep 14 '12 at 19:21

2 Answers2

6

#define _snprintf_s(a,b,c,...) snprintf(a,b,__VA_ARGS__)

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Thanks for your response! I tried your suggestion and it worked. – sskanitk Sep 14 '12 at 22:04
  • This may lead to bugs as MS's Appendix K functions provide a terminator, which snprintf doesn't if the buffers filled. The ported code may be buggy as a result – Rob11311 Jun 27 '14 at 22:51
  • @Rob11311: `snprintf` always terminates unless `n` is zero, in which case it writes nothing. Your information is simply incorrect. Read C99. – R.. GitHub STOP HELPING ICE Jun 28 '14 at 01:21
  • [C99 7.19.6.5 The snprintf function](http://port70.net/~nsz/c/c99/n1256.html#7.19.6.5). – R.. GitHub STOP HELPING ICE Jun 28 '14 at 01:29
  • Well that standard specification is clear (at last!) thanks for the link. The POSIX man pages still aren't and in past snprintf has even ignored size completely and just been a call to sprintf on some versions of libc leading to security holes. That kind of thing is probably why I never liked trusting an implementation snprintf. Think I'll try and submit a bug report and patch to the Linux man pages, as GCC has C99 & C11 support. – Rob11311 Jun 28 '14 at 09:43
  • I've actually checked back in an old copy of Harbison & Steel, and the `draft proposed ANSI C` specification for strncpy(3) is poorly defined and ugly, allowing non-termination. Probably consequences of documenting previously unstandardised practice, rather than specifying a truly useful function. Porting between implementations could throw up new holes as a consequence and that's likely lead to vagueness about snprintf to. – Rob11311 Jun 28 '14 at 09:54
  • POSIX 2008 is clear now as well, so I ought to have said the current Linux man pages (based on older versions of POSIX). – Rob11311 Jun 28 '14 at 10:52
  • @Rob11311: I don't know where you got all that information. I cannot find any "POSIX man page" that says `snprintf` does not terminate. Nor any evidence of any libc where `snprintf` ignored `n` and called `sprintf`. Some *broken applications* use such a fallback themselves when the system they're compiled for lacks `snprintf`, but that's purely an application bug. Also not sure what your point about `strncpy` is. It's completely unrelated to `snprintf` and should never be used except for fixed-size, non-terminated fields. – R.. GitHub STOP HELPING ICE Jun 28 '14 at 15:45
  • @Rob11311: At this point I can't help but conclude that you're trolling. One moment you're claiming `snprintf` isn't safe because of a bug in a library that hasn't been used in 20 years and that predates the introduction of `snprintf` in the standard. The next moment you're bringing up irrelevant, unrelated functions like `strncpy` (which has nothing to do with safe string handling but rather working with fixed-length, non-C-string fields in directory tables on ancient versions of Unix and similar data structures). – R.. GitHub STOP HELPING ICE Jun 29 '14 at 02:36
  • BTW I'll ask Michael to remove that misleading information from the man page in case it's hurting acceptance of `snprintf`. libc4 is literally 20 years old and was never present on any version of Linux that was usable for anything but as a toy/experiment. – R.. GitHub STOP HELPING ICE Jun 29 '14 at 02:38
  • Calm down R., my doubts on the redefine are down to UTF-8, because MS mentions characters (and windows code may have trouble with UTF-8 if they expect wchars), and the lack of clarity on termination of the Linux manpage, which looks like C99 & POSIX 2008 has fixed. snprintf needs clear man page like C99/POSIX2008 on termination because of strncpy which was designed for old UNIX fixed field inode/14 char directories. Thank you for posting link to the C99 spec, that was helpful. – Rob11311 Jun 29 '14 at 03:15
  • In the terminology of the C language, "characters" means bytes. The printf-family functions do not work with multibyte characters except that the format string is required to be valid as a multibyte string (this is to ensure that `%` can be processed unambiguously) and of course the `%ls` format handles conversion of wide characters to multibyte characters. But all field widths/precisions, the `snprintf` buffer size, and the return values are measured in bytes. – R.. GitHub STOP HELPING ICE Jun 29 '14 at 03:19
5

Instead of a macro, you could implement _snprintf_s() as a function. This simple (untested) version doesn't perform the runtime error handling that _snprintf_s() is supposed to do (you could add that if needed/desired), but it attempts to deal with the differences that the count parameter imposes and the return value on truncation:

#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#if !defined(_TRUNCATE)
#define _TRUNCATE ((size_t)-1)
#endif

int _snprintf_s(
   char *buffer,
   size_t sizeOfBuffer,
   size_t count,
   const char *format,
   ... 
)
{
    int retval;
    va_list ap;

    if ((count != _TRUNCATE) && (count < sizeOfBuffer)) {
        sizeOfBuffer = count;
    }

    va_start(ap, format);
    retval = vsnprintf(buffer, sizeOfBuffer, format, ap);
    va_end(ap);

    if ((0 <= retval) && (sizeOfBuffer <= (size_t) retval)) {
        retval = -1;
    }

    return retval;
}

(Why is the count parameter in the _snprintf_s() parameter list in the first place?)

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • Thanks for the implementation. Actually, I do need just an equivalent of snprintf and following the approach suggested in the answer posted below, seems to do what I need. – sskanitk Sep 14 '12 at 22:05
  • +1 for final parenthesized remark. I can't make any sense of the MS "bounds checking interfaces" and the duplicate length/count arguments they take. Perhaps the idea is that if you have to write the destination buffer size twice, you're more likely to get it right...? – R.. GitHub STOP HELPING ICE Sep 14 '12 at 22:15
  • The MS functions do a "chop" to, to ensure a truncated string is NULL terminated. – Rob11311 Jun 27 '14 at 23:10
  • 1
    The `sizeOfBuffer` and `count` parameters are not actually duplicating information. `sizeOfBuffer` is in bytes, and `count` is in characters. Characters and bytes are different units, and there's no easy conversion between the two that works for all encodings and strings. – chwarr May 10 '16 at 06:04