I noticied a (to my opinion) very strange behaviour of snprintf with c++ on several platforms. Consider the following code (minimum working example which causes the observed behaviour):
#include <stdio.h>
char test1[512];
char test2[512];
char test3[1024];
char test4[1024];
int main()
{
snprintf(test1, sizeof(test1), "test1");
snprintf(test2, sizeof(test2), "test2");
snprintf(test3, sizeof(test3), "%s %s", test1, test2);
return 0;
}
When running though valgrind with --tool=exp-sgcheck, the following error is reported (for the 3rd snprintf statement):
==30302== Invalid read of size 1
==30302== at 0x568E4EB: vfprintf (in /lib64/libc-2.19.so)
==30302== by 0x56B7608: vsnprintf (in /lib64/libc-2.19.so)
==30302== by 0x5695209: snprintf (in /lib64/libc-2.19.so)
==30302== by 0x4006AD: main (1.cc:12)
==30302== Address 0x601460 expected vs actual:
==30302== Expected: global array "test1" of size 1,024 in object with soname "NONE"
==30302== Actual: global array "test2" of size 512 in object with soname "NONE"
==30302== Actual: is 0 after Expected
So passing test1 as argument to the first %s leads to a read after the end of the test1 array.
This behaviour caused several page faults in a windows driver (yes I know it's static data...). Luckily the code is portable, and when ported to linux valgrind reported that error.
But to my knowledge, snprintf should terminate test1 with \0 at the 6th byte (which it does, checked that). So why causes the 3rd snprintf statement a read after the end of the test1 array? Changing the 3rd snprintf statement to
snprintf(test3, sizeof(test3), "%.512s %s", test1, test2);
solves the problem on both platforms. Compiling the code as C code (not C++) results in no error.
UPDATE: On linux (and maybe windows as well) the error only occurs, if the code is compiled with debug information included and optimizations disabled (-g -O0 for gcc).
Since global objects (like the arrays in your example) are 0-initialized the last snprintf should never read beyond the string's end, independent of whether the previous sprintfs copied the terminating 0 char or not. The only explanation is that the previous snprintfs copied much more than the submitted "test1" to the target test1
, overwriting all the 0s with non-0s (no 0s would btw be unlikely with random memory).
That is very unlikely -- such an obvious bug would have been found earlier. With respect to the error in the driver I would suspect that the memory is being overwritten by a completely unrelated "process" (in a general sense, maybe another driver). For a desktop application I have no explanation why it would fail. Trying your example on Codingground with gcc 4.8.3 was running just fine and printed the expected strings when I added a printf() at the end.
Btw, it's not surprising that the original code runs fine with optimizations enabled: Since there is no observable effect the compiler may emit just a NOP.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With