There are a number of differences between the snprintf and sprintf_s functions, notably in their return values and how they handle errors.
Return Values (barring errors):
snprintfreturns the number of characters which would have been written to the buffer if the "size" argument were ignored.sprintf_sreturns the number of characters actually written.
Additional Checks:
The sprintf_s function also performs checks that snprintf does not, including. The call fails (and returns zero) if any of the following are true:
- The
%nformat specifier is given. - Any of the arguments corresponding to a
%sformat specifier are null pointers. - The given "size" argument is zero.
There are a number of differences between the snprintf and sprintf_s functions, notably in their return values and how they handle errors.
Return Values (barring errors):
snprintfreturns the number of characters which would have been written to the buffer if the "size" argument were ignored.sprintf_sreturns the number of characters actually written.
Additional Checks:
The sprintf_s function also performs checks that snprintf does not, including. The call fails (and returns zero) if any of the following are true:
- The
%nformat specifier is given. - Any of the arguments corresponding to a
%sformat specifier are null pointers. - The given "size" argument is zero.
The main differences between snprintf and sprintf_s are:
- The function
snprintfis available on all ISO C compliant platforms, whereas the functionsprintf_sdoes not exist on most platforms. This is because compliant platforms are not required to implement Annex K of the standard and most platforms have chosen not to implement it. - The function
snprintfwill silently truncate the string if it is too large, whereas the functionsprintf_swill call the currently installed contraint handler function. However, withsnprintf, it is possible to detect whether a silent truncation occurred, by inspecting the function's return value. - The function
sprintf_swill perform additional validation of the function arguments (such as checking for aNULLpointer) and will call the currently installed constraint handler function if these validations fail, whereas callingsnprintfwith an invalid argument will invoke undefined behavior (i.e. possibly crash the program).
Videos
Haven't written any C++ for a while and just ran into snprintf() when XCode complained that sprintf() is deprecated. snprintf() is supposed to be "safer" than sprintf. But this seems to overwrite the buffer happily:
char s[5];
snprintf(s, 64, "%s\n", "Too long for your buffer");
printf("%s", s);It only stops after it overflows the buffer and prints the string. Then it figures out the stack has been corrupted.
Safer?
So I recently discovered that Visual Studio 2019 apparently disables the sprintf function by default and says to consider using their version, sprintf_s instead. It won't even compile code that uses it unless I specifically disable the warning.
This seems very odd since sprintf is a standard C library function and, AFAIK, using it isn't against the standard usage guidelines or best practices, unlike, e.g. using goto's. So what's up with this? If it's really unsafe, why hasn't a safer version of it already been written for the standard library? And if it's not unsafe, why is Visual Studio complaining about it?
And should I use sprintf_s instead? My concern with doing that is that I suspect other compilers wouldn't recognize it and so the code wouldn't be portable, plus Microsoft isn't really clear on the proper syntax for it.
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
sizeparameter in Linux andcountparameter 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.
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).
The two expressions you gave are not equivalent: sprintf takes no argument specifying the maximum number of bytes to write; it simply takes a destination buffer, a format string, and a bunch of arguments. Therefore, it may write more bytes than your buffer has space for, and in so doing write arbitrary code. The %.*s is not a satisfactory solution because:
- When the format specifier refers to length, it's referring to the equivalent of
strlen; this is a measure of the number of characters in the string, not its length in memory (i.e. it doesn't count the null terminator). - Any change in the format string (adding a newline, for example) will change the behavior of the
sprintfversion with respect to buffer overflows. Withsnprintf, a fixed, clear maximum is set regardless of changes in the format string or input types.
The best and most flexible way would be to use snprintf!
size_t nbytes = snprintf(NULL, 0, "%s", name) + 1; /* +1 for the '\0' */
char *str = malloc(nbytes);
snprintf(str, nbytes, "%s", name);
In C99, snprintf returns the number of bytes written to the string excluding the '\0'. If there were less than the necessary amount of bytes, snprintf returns the number of bytes that would have been necessary to expand the format (still excluding the '\0'). By passing snprintf a string of 0 length, you can find out ahead of time how long the expanded string would have been, and use it to allocate the necessary memory.
I am using the sprintf function to create file names in the form of 000.jpg. Here is a line I used:
sprintf(file_name, "%03i.jpg", images);
But my text editor warned me about sprintf:
Call to function 'sprintf' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'sprintf_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]
It recommends using sprintf_s. But I do not know how to use it and I did not see tutorials for it.