strncpy doesn't do what you think

/* This is the behavior a reasonable person would have given to strncpy, but
 * its not the behavior strncpy has. */
char *reasonable_strncpy(char *dst, const char *src, size_t dst_size) {
    if (dst_size != 0) {
        dst_size--;
        for (; *src && dst_size; ++src, --dst_size) {
            *dst++ = *src;
        }
        *dst = 0;
    }
    return dst;
}

This post is inspired by Lars Wirzenius' "strncpy? just say no," which made the rounds on proggit a day or two ago. The post points out the first of strncpy's two unexpected behaviors. I'm writing this to point out the second. But for completeness, I point them both out.

Behind Door #1

The first suprise:

strncpy doesn't guarantee that the resulting string in dst is NUL terminated. If there isn't enough space, the result is merely truncated.

Consider the following concrete example:

#include <stdio.h>
#include <string.h>
int main(void) {
    char buf[8] = "XXXXXXX";
    strncpy(buf, "x-ray", 4);
    puts(buf);
    return 0;
}

prints x-rXXXX, not x-. If you pass strncpy the size of your buffer as the third parameter, it may leave you with an unterminated string.

Behind Door #2

The second unexpected behavior comes up much less often, but I've seen it cause bugs in real code:

strncpy will always write dst_size bytes to dst, even if the source string is smaller.

For example:

#include <stdio.h>
#include <string.h>
int main(void) {
    char buf[16];
    strcpy(buf+8, "back");
    strncpy(buf, "front", 16);
    puts(buf+8);
    return 0;
}

prints nothing, rather than back.

The Solution

If truncation is acceptable:

strncpy(dst, src, sizeof dst - 1);
dst[sizeof dst - 1] = 0;

/* snprintf was added in C99, but it or an equivalent function is present in
 * most C89 implementations. */
snprintf(dst, sizeof dst, "%s", src);

If truncation isn't acceptable, you should consider whether or not C is the best language for the task at hand. If it really is, then you should use one of the many C string libraries. bstring is popular, portable and liberally licensed, although I've never used it myself.