(guest@joequery.me)~ $ |

C snprintf tutorial: explanation and examples

snprintf is essentially a function that redirects the output of printf to a buffer. This is particularly useful for avoiding repetition of a formatted string. You can build a string once and use printf("%s", mystr) instead of print("%d,%s,%f,%d", x,y,z,a) every time, which gets cumbersome with actual variable names.

snprintf vs sprintf

snprintf is extremely similar to sprintf, which can be found on the same manpage. After all, the names of the functions only differ by the character 'n'! This is actually a pretty common convention in C: the function with the 'n' will require an upper bound or maximum size as an argument. For the most part, the 'n' version of functions are safer and less susceptible to buffer overflows. A significant exception to this rule is strncpy.

Using snprintf

The function header for snprintf is

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

*str is the buffer where printf output will be redirected to. size is the maximum number of bytes(characters) that will be written to the buffer.

The format and the optional ... arguments are just the string formats like "%d", myint as seen in printf.

Two very important things to note here:

  1. snprintf automatically appends a null character to the character sequence resulting from format substitution
  2. This automatically appended character is not exempt from the size check.

This means "%d", 100 will occupy 4 bytes went 'redirected' to the *str buffer

[ 1 | 0 | 0 | \0 ]

In this example, the buffer must be of at least size 4, and the size parameter must be at least 4. If the buffer size is <4 but the size argument is >=4, you will write past the bounds of the buffer (undefined behavior!). If the buffer size >=4 but the size argument is <4, you won't experience undefined behavior, but your resulting string will be truncated.

Consequently, it's very common to have the size of the buffer passed as the size argument.

Understanding the effect of size

In the following example, we take a buffer, initialize all its elements, and run some experiments. We'll alter both the size portion of the call to snprintf as well as the size of the string we're attempting to place into the buffer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include<stdio.h>
#define BUFSIZE 9

void init_buf(char *buf, size_t size);
void print_buf(char *buf);

int main(){
    char buf[BUFSIZE];
    init_buf(buf, BUFSIZE);
    print_buf(buf);

    // hello there! == 12 characters, > BUFSIZE
    init_buf(buf, BUFSIZE);
    snprintf(buf, BUFSIZE, "hello there!");
    print_buf(buf);

    // turtle == 6 charaters, < BUFSIZE
    init_buf(buf, BUFSIZE);
    snprintf(buf, BUFSIZE, "turtle");
    print_buf(buf);

    // 2222220 == 7 charaters, > 5
    init_buf(buf, BUFSIZE);
    snprintf(buf, 5, "%d", 222222 * 10);
    print_buf(buf);

    return 0;
}

void init_buf(char *buf, size_t size){
    int i;
    for(i=0; i<size; i++){
        buf[i] = i + '0'; // int to char conversion
    }
}

void print_buf(char *buf){
    int i;
    char c;
    for(i=0; i<BUFSIZE; i++){
        c = buf[i];
        if(c == '\0'){
            printf("\\0");

        }
        else{
            printf("%c", buf[i]);
        }
    }
    printf("\n");
}

raw source

The output:

012345678
hello th\0
turtle\078
2222\05678

Let's say n is the index at which snprintf places the null character. n can be formulated as

n = min(size of format string after substitution, size-1)

(By "after substitution", I mean if we had "%d",1000, the format string after substitution is "1000" and has a size of 4.)

You should take a few minutes to verify this formula for all the examples above :)

The return value

Now that we understand how the format string and the size argument influence where snprintf places the null character, we can now discuss the return value of snprintf and its usefulness.

snprintf returns the size of the format string after substitution. If the size of the format string after substitution is greater than or equal to size, that means the string was truncated. If your buffer size is also equal to size (which it should be when using this function), then this truncation implies the string was too big to fit in the buffer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#define BUFSIZE 10

int main(){
    char buf[BUFSIZE];
    if(snprintf(buf, BUFSIZE, "hello") >= BUFSIZE){
        printf("%s\n", buf);
    }

    if(snprintf(buf, BUFSIZE, "An extremely long string") >= BUFSIZE){
        printf("buf: %s\n", buf);
    }

    if(snprintf(buf, BUFSIZE, "0%d", 123456789) >= BUFSIZE){
        printf("buf: %s\n", buf);
    }

    return 0;
}

raw source

The output:

buf: An extrem
buf: 012345678

The first if statement evaluates to false since the size of the string "hello" is 6, which is less than the BUFSIZE of 10. The second and third statements evaluate to true since the strings after substitution have a size greater than or equal to BUFSIZE.

A conservative buffer

A good way to demonstrate snprintf is to implement a "conservative buffer". We will allocate a small amount of memory for a buffer, attempt to place a string into it, and create a larger buffer if necessary. If the string still doesn't fit after we create the larger buffer, we'll exit the program.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<stdlib.h>

int main(){
    int bufSize = 10;
    char *mystr = "This is my string!";
    char *buf = malloc(bufSize);

    if(snprintf(buf, bufSize, "%s", mystr) >= bufSize){
        bufSize *= 2;
        printf("Not enough space. Trying %d bytes\n", bufSize);
        free(buf);
        buf = malloc(bufSize);

        if(snprintf(buf, bufSize, "%s", mystr) >= bufSize){
            printf("Still not enough space. Aborting\n");
            exit(1);
        }
    }

    printf("There was enough space!\n");
    printf("buf: %s\n", buf);
    return 0;
}

raw source

The output:

Not enough space. Trying 20 bytes
There was enough space!
buf: This is my string!

However, if bufSize was initially set to 5, the output would be the following:

Not enough space. Trying 10 bytes
Still not enough space. Aborting

Tagged as c

Date published - December 15, 2012