Step 6 of 8
Safe formatting with snprintf
sprintf(buf, format, ...) works like printf but writes into a char array. The problem: it has no idea how big buf is, so a long name or a big number can write past the end. That exact bug has caused countless security holes.
snprintf takes the buffer size as its second argument and never writes more than that, always ending with a '\0' (as long as the size isn't 0):
#include <stdio.h>
int main(void) {
volatile int score = 12345;
char small[8];
int needed = snprintf(small, sizeof small, "score=%d", score);
printf("[%s] needed %d\n", small, needed);
char big[32];
needed = snprintf(big, sizeof big, "score=%d", score);
printf("[%s] needed %d\n", big, needed);
return 0;
}
[score=1] needed 11
[score=12345] needed 11
(volatile just stops the compiler from working out the value in advance and warning about the truncation we're demonstrating on purpose.)
Detecting truncation
The return value is the length the full output would have had (not counting the terminator), whether or not it fit. So:
- If
needed >= size, the output was cut off (truncated). In the first call, 11 characters didn't fit in 8 bytes, so only 7 were kept plus the'\0'. - If
needed < 0, an encoding error happened (rare).
A careful function reports truncation to its caller instead of silently producing a clipped string. Since needed is an int and size a size_t, compare them as (size_t)needed >= size after checking needed isn't negative.
Use snprintf for all formatting into buffers; there's no good reason to use sprintf in new code.
Your turn: write int format_kd(char *buf, size_t size, const char *name, int kills, int deaths) that writes NAME K/D (like ropz 25/12). Return 0 if it fit, -1 if it was truncated.