Consider the following C code:
#include <inttypes.h> //PRIu64
#include <stdio.h>
#define PATH_MAX 260
int main()
{
char mydir[] = "HEH_SO_TRYINIT_HERE";
char mydir_path[PATH_MAX] = "";
char other_path[PATH_MAX] = "";
snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir );
printf("%s\n", mydir_path);
return 0;
}
If I build this with gcc
on MINGW64 (gcc.exe (Rev2, Built by MSYS2 project) 14.2.0), Windows 10, I get the following warnings (same warnings can be obtained if you also use onlinegdb to try this example):
$ gcc -g -Wall -std=c99 test.c -o test.exe
test.c: In function 'main':
test.c:11:51: warning: '%s' directive output may be truncated writing up to 20 bytes into a region of size between 0 and 259 [-Wformat-truncation=]
11 | snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir );
| ^~ ~~~~~
test.c:11:5: note: 'snprintf' output between 2 and 281 bytes into a destination of size 260
11 | snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir );
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now, this puzzles me a lot, because I thought that by using snprintf
, I'm already hinting to the compiler that I'm aware that a truncation might occur, so I expected it would not notify me about that.
Which is why I am at a loss: what problem is the compiler trying to notify me of here, and how should I anize this code, so it compiles without warning (preferably without switching off, in this case, -Wformat-truncation
)?
Consider the following C code:
#include <inttypes.h> //PRIu64
#include <stdio.h>
#define PATH_MAX 260
int main()
{
char mydir[] = "HEH_SO_TRYINIT_HERE";
char mydir_path[PATH_MAX] = "";
char other_path[PATH_MAX] = "";
snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir );
printf("%s\n", mydir_path);
return 0;
}
If I build this with gcc
on MINGW64 (gcc.exe (Rev2, Built by MSYS2 project) 14.2.0), Windows 10, I get the following warnings (same warnings can be obtained if you also use onlinegdb to try this example):
$ gcc -g -Wall -std=c99 test.c -o test.exe
test.c: In function 'main':
test.c:11:51: warning: '%s' directive output may be truncated writing up to 20 bytes into a region of size between 0 and 259 [-Wformat-truncation=]
11 | snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir );
| ^~ ~~~~~
test.c:11:5: note: 'snprintf' output between 2 and 281 bytes into a destination of size 260
11 | snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir );
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now, this puzzles me a lot, because I thought that by using snprintf
, I'm already hinting to the compiler that I'm aware that a truncation might occur, so I expected it would not notify me about that.
Which is why I am at a loss: what problem is the compiler trying to notify me of here, and how should I anize this code, so it compiles without warning (preferably without switching off, in this case, -Wformat-truncation
)?
3 Answers
Reset to default 5Now, this puzzles me a lot, because I thought that by using
snprintf
, I'm already hinting to the compiler that I'm aware that a truncation might occur, so I expected it would not notify me about that.
Well, yes and no. By using snprintf()
, you're most directly ensuring that no more than the specified number of bytes is written. Yes, that conveys a willingness for the output to be truncated, but that does not necessarily mean that you consider truncation acceptable to the point of not being noteworthy.
For instance, it is relatively common practice in some circles to use snprintf()
and similar bounded-output functions because they are perceived to be safer than some of their alternatives, such as sprintf()
. Such a policy is mostly a hedge against sloppy programming, where the intent is not to accept that truncation is ok, but rather to prefer truncation over a bounds overrun.
Which is why I am at a loss: what problem is the compiler trying to notify me of here, and how should I anize this code, so it compiles without warning (preferably without switching off, in this case,
-Wformat-truncation
)?
The compiler is warning you about exactly what you think it is warning you about: the string emitted by the snprintf()
call may be truncated relative to the one that would be produced into a larger output buffer. Your expectation that the compiler would not warn about this for snprintf()
simply is not satisfied. If you don't want to be warned then either ensure that the output buffer is large enough to avoid the possibility of truncation:
char mydir[] = "HEH_SO_TRYINIT_HERE";
char other_path[PATH_MAX] = "";
char mydir_path[sizeof(other_path) + sizeof(mydir)] = "";
snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir );
... or ensure that the compiler does not have enough information to compute the maximum needed size at compile time (difficult with only one translation unit), or disable the warning.
If you choose to disable the warning, then it may be that you can make that more palatable by doing so only for that particular function call. For instance, GCC has pragmas for locally enabling and disabling warnings. Of course, like most aspects of diagnostic messaging, this is implementation specific.
mydir_path
could be defined as a pointer.
Use snprintf
to get the size of the output, then allocate memory.
#include <stdio.h>
#include <stdlib.h>
#define PATH_MAX 260
int main()
{
char mydir[] = "HEH_SO_TRYINIT_HERE";
char other_path[PATH_MAX] = "";
char *mydir_path = NULL;
size_t size = snprintf ( NULL, 0, "%s/%s", other_path, mydir);
if ( NULL != ( mydir_path = malloc ( size + 1))) { // + 1 for terminating zero
snprintf ( mydir_path, size, "%s/%s", other_path, mydir);
printf ( "%s\n", mydir_path);
free ( mydir_path);
}
else {
fprintf ( stderr, "problem malloc\n");
}
return 0;
}
Another option would be to use the precision field to limit the number of characters printed.
The specifier %.*s
allows for a variable for the number of characters for the precision.
#include <stdio.h>
#define PATH_MAX 260
int main()
{
char mydir[] = "HEH_SO_TRYINIT_HERE";
char other_path[PATH_MAX] = "";
char mydir_path[PATH_MAX] = "";
int limit = PATH_MAX - 1 - (int)sizeof mydir; // - 1 for the / in the format string
snprintf ( mydir_path, sizeof mydir_path, "%.*s/%s", limit, other_path, mydir);
printf ( "%s\n", mydir_path);
return 0;
}
Another option to "silence" warning is to handle situation when buffer is to small.
snprintf
returns The number of characters that would have been written if n had been sufficiently large
, not actually written.
#include <inttypes.h> //PRIu64
#include <stdio.h>
#define PATH_MAX 260
int main()
{
char mydir[] = "HEH_SO_TRYINIT_HERE";
char mydir_path[PATH_MAX] = "";
char other_path[PATH_MAX] = "";
if (snprintf( mydir_path, sizeof(mydir_path), "%s/%s", other_path, mydir ) > (int)sizeof(mydir_path)) {
printf("error\r\n");
}
printf("%s\n", mydir_path);
return 0;
}
In that case compiler knows you have handled both cases - when whole string fits into buffer and when it's truncated.
MAX_PATH
, but you are then (possibly) trying to writeMAX-PATH
plus ` 1 (backslash) + 20 characters to it. The compiler is just warning you that you may end up with a truncated string. The size limit tosnprintf
only takes effect at run time when the function writes the maximum characters into the buffer. – OldBoy Commented Feb 17 at 17:29char mydir_path[PATH_MAX + sizeof mydir + 1] = "";
be used? – xing Commented Feb 17 at 18:37mydir_path[PATH_MAX + sizeof mydir]
would be enough in this case, becausePATH_MAX
andsizeof mydir
both account for a terminator, but you need only one. – John Bollinger Commented Feb 17 at 19:13