Part of C in 100s

C in 100 Seconds: sprintf and snprintf

Celest KimCelest Kim

Video: C in 100 Seconds: sprintf and snprintf — Format Into Strings | Episode 34 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C sprintf and snprintf: Format Into Strings

sprintf(buf, "fmt", args) formats into a buffer. snprintf(buf, size, "fmt", args) is the safe version that never overflows. Always use snprintf.

sprintf is printf for strings — same format specifiers, but the result goes into a buffer instead of stdout. Used for building log messages, paths, structured strings.

The basic shape

#include <stdio.h>

int main() {
  char msg[100];
  sprintf(msg, "Hello, %s! You are %d.", "Alice", 30);
  printf("%s\n", msg);   // Hello, Alice! You are 30.

  char path[100];
  int id = 7;
  sprintf(path, "/tmp/file_%03d.txt", id);
  printf("%s\n", path);   // /tmp/file_007.txt

  // snprintf — safe version
  char small[15];
  int n = snprintf(small, sizeof(small), "This is a very long string");
  printf("%s\n", small);   // truncated
  printf("needed: %d chars\n", n);   // 26 (the full length)

  return 0;
}

sprintf

int sprintf(char *str, const char *format, ...);

Formats according to format and writes the result to str. Returns the number of characters written (excluding \0).

No length check. If the formatted output is longer than the buffer, sprintf writes past the end. Buffer overflow.

char buf[10];
sprintf(buf, "%d", 9999999999);   // overflow — needs 11 bytes

Avoid sprintf. Always prefer snprintf.

snprintf — the safe version

int snprintf(char *str, size_t size, const char *format, ...);

Writes at most size - 1 characters and always null-terminates (unless size is 0).

char buf[15];
int n = snprintf(buf, sizeof(buf), "This is a very long string");
// buf is "This is a very" (14 chars + \0)
// n is 26 (length needed if buffer were big enough)

The return value is the full required size, not what was actually written. To detect truncation:

int n = snprintf(buf, sizeof(buf), "...");
if (n >= (int)sizeof(buf)) {
  // truncated — n shows what would have fit
}

Format specifiers (same as printf)

sprintf(msg, "%d", 42);                    // "42"
sprintf(msg, "%.2f", 3.14159);             // "3.14"
sprintf(msg, "%s", "hello");                // "hello"
sprintf(msg, "%c", 'A');                    // "A"
sprintf(msg, "%p", (void*)&x);              // "0x..."
sprintf(msg, "%x", 255);                    // "ff"
sprintf(msg, "[%10d]", 42);                 // "[        42]"
sprintf(msg, "[%-10d]", 42);                // "[42        ]"
sprintf(msg, "[%010d]", 42);                // "[0000000042]"

Episode 3 covers the format mini-language in detail. Same rules apply here.

Building a path

int id = 7;
char path[100];
snprintf(path, sizeof(path), "/tmp/file_%03d.txt", id);
// "/tmp/file_007.txt"

%03d zero-pads to width 3. Common pattern for sequential filenames.

Building a log line

char log[100];
snprintf(log, sizeof(log), "[%s] %s: %d",
  "INFO", "temperature", 42);
// "[INFO] temperature: 42"

Multiple fields, one line. Useful for log aggregation (each line a complete record).

Concatenating safely

char buf[100] = "";
size_t pos = 0;

pos += snprintf(buf + pos, sizeof(buf) - pos, "Hello, ");
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s", name);
pos += snprintf(buf + pos, sizeof(buf) - pos, "!");

Each call advances pos by what was written, and uses the remaining size. Never overflows. Replaces unsafe strcat chains.

For complex builders, this is the canonical "string builder" pattern in C.

sprintf returning length

int n = sprintf(buf, "value: %d", 42);
// n is the number of bytes written (excluding \0)
// e.g., "value: 42" — 9 bytes — n = 9

For sprintf, return is what was written. For snprintf, return is what would have been written (for the user's truncation check).

If snprintf(NULL, 0, fmt, ...) returns the size needed without writing — useful for sizing a dynamic allocation:

int n = snprintf(NULL, 0, "id=%d, name=%s", id, name);
char *buf = malloc(n + 1);
snprintf(buf, n + 1, "id=%d, name=%s", id, name);

Two-pass: first to size, then to write. Avoids both overflow and arbitrary buffer sizes.

In-place modification

char buf[100] = "Initial value";
sprintf(buf, "%s — modified", buf);   // UNDEFINED BEHAVIOR

Source and destination overlap → undefined. Use a separate buffer:

char src[100] = "Initial value";
char dst[120];
snprintf(dst, sizeof(dst), "%s — modified", src);

Common mistakes

sprintf instead of snprintf. Almost always wrong. Use snprintf except when you've proven the output fits.

Misinterpreting snprintf return. It's the size needed, not written. Check n >= size for truncation.

Buffer too small. Truncation silently loses data. Always size generously or check the return.

Format mismatch with args. sprintf(buf, "%d", 3.14); — undefined; printf family is variadic, no compile-time check beyond what the compiler chooses to emit.

Reentrant problem? Not for sprintf. sprintf doesn't have hidden state like strtok — each call is independent. Safe in multithreaded contexts (as long as different threads use different buffers).

What's next

Episode 35: file I/O — fopen, fgets, fgetc. Reading from files.

Recap

snprintf(buf, size, fmt, args) to format into a buffer safely. Always use snprintf, not sprintf. Returns the size needed — check n >= size for truncation. snprintf(NULL, 0, fmt, args) returns the size without writing (size-then-allocate idiom). For chained writes, advance pos by the return; pass buf+pos and size-pos each time. Never use overlapping source/destination.

Next episode: file I/O reading.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.