The String Copy Showdown: Choosing Between strcpy, strncpy, and strlcpy for Safer C Code
Ask a room full of C developers which string copy function they trust, and you will hear three different answers — each delivered with complete confidence. That disagreement is not a coincidence. The trio of strcpy, strncpy, and strlcpy has accumulated decades of competing documentation, platform-specific behavior, and stubborn mythology. The result is a landscape where well-intentioned engineers routinely reach for the wrong tool, sometimes with serious consequences.
This article sets the record straight. We will examine each function on its merits, expose the misconceptions that have survived far too long, and close with a practical decision framework you can keep open in a browser tab during your next code review.
strcpy: The Original, and Still Dangerous
strcpy(dest, src) is the oldest of the three and the simplest to understand: it copies bytes from src into dest, including the null terminator, and stops when it hits that terminator. There is no length argument. There is no boundary check. If src is longer than the buffer dest points to, strcpy will write past the end of that buffer without hesitation.
Buffer overflows triggered by unchecked strcpy calls have been documented in CVE databases going back to the earliest days of public vulnerability reporting. The function is not inherently evil — it is simply indifferent to your memory layout. In tightly controlled environments where the source string length is guaranteed at compile time and the destination buffer is sized accordingly, strcpy is both correct and fast. Outside of those conditions, it is a liability.
The honest verdict: Use strcpy only when you can prove — not assume — that the source will never exceed the destination buffer. In practice, that proof is rarer than most developers admit.
strncpy: The 'Safe' Alternative That Isn't
Here is where the folklore gets dangerous. strncpy(dest, src, n) accepts a maximum byte count, which leads many developers to treat it as a straightforward safety upgrade from strcpy. That assumption is wrong in two distinct ways.
First, strncpy does not guarantee null termination. If src is at least n bytes long, the function copies exactly n bytes and writes no null terminator to dest. The resulting buffer is not a valid C string. Any subsequent operation that expects one — strlen, printf with %s, another string copy — will read past the buffer boundary, producing undefined behavior or a crash.
Second, when src is shorter than n, strncpy zero-pads the remainder of the destination buffer all the way to n bytes. This behavior was designed for a specific historical use case: fixed-width record fields in early Unix filesystems, where padding was intentional and null termination was handled separately. For general-purpose string copying, that zero-padding is wasted work — and in performance-sensitive code, it is measurably slower than alternatives.
Benchmarks on modern x86-64 hardware consistently show strncpy lagging behind both strcpy and strlcpy when copying short strings into large buffers, precisely because of that padding obligation. The function is doing work you almost certainly did not ask for.
The honest verdict: strncpy is not a safe version of strcpy. It is a different function with a different contract, one that was never intended for the general-purpose role developers have assigned it. Stop using it as a drop-in replacement.
strlcpy: The Pragmatic Choice Most Platforms Now Support
strlcpy(dest, src, size) was introduced by OpenBSD developers Todd Miller and Théo de Raadt in 1998 specifically to address the shortcomings described above. Its contract is clean: copy at most size - 1 bytes from src into dest, always null-terminate dest, and return the total length of src. That return value is particularly useful — it allows callers to detect truncation by comparing the return value against the destination buffer size.
For years, strlcpy was the function C developers on Linux had to either vendor into their projects or compile conditionally, since glibc historically declined to include it. That situation has shifted. As of glibc 2.38, released in 2023, strlcpy is part of the standard library on Linux. Combined with its long-standing availability on macOS, BSD systems, and via the OpenBSD-derived implementations bundled in many embedded toolchains, the portability argument against strlcpy has largely expired.
Performance comparisons favor strlcpy in most common scenarios. Because it does not zero-pad and because it can be implemented with a single forward scan, it outperforms strncpy on short-to-medium strings and matches strcpy closely when no truncation occurs.
The honest verdict: For the majority of string copy operations in modern C code, strlcpy is the correct default. It is safe, predictable, and increasingly portable.
Platform Realities and Compiler Warnings
One practical consideration that often goes undiscussed: compiler and static analysis tooling increasingly flags strcpy and strncpy usage. GCC and Clang both emit warnings under -Wstringop-overflow and related flags when they can detect potential overflows. Microsoft's Visual C++ has treated strcpy as deprecated in favor of strcpy_s for well over a decade. If your build pipeline enforces zero-warning policies — and it should — you will encounter friction with both strcpy and strncpy in contexts the compiler cannot fully analyze.
strcpy_s, the bounds-checking variant introduced in Annex K of C11, is worth mentioning here. It is available on Windows and via some third-party implementations on other platforms, but its adoption has been uneven and its error-handling semantics are more complex than strlcpy. For teams working in cross-platform environments, strlcpy remains the more pragmatic path.
The Decision Cheat Sheet
Bookmark this. It will save you time during code review.
| Scenario | Recommended Function | Reason |
|---|---|---|
| Source length is provably bounded at compile time, buffer is correctly sized | strcpy |
Maximum simplicity, zero overhead |
| You need null-terminated output with truncation detection | strlcpy |
Safe, portable (post-glibc 2.38), returns source length |
| You are working with fixed-width, non-null-terminated record fields | strncpy |
This is what it was actually designed for |
| You are on Windows and targeting MSVC | strcpy_s |
Aligns with platform conventions and suppresses deprecation warnings |
You cannot use strlcpy and need a safe alternative |
Implement a thin wrapper around snprintf(dest, size, "%s", src) |
Widely available, null-terminates, handles truncation |
Avoid using strncpy as a general safety net. Avoid using strcpy in any context where input is not fully controlled. Default to strlcpy when in doubt.
The Takeaway
The C standard library's string handling functions carry the weight of fifty years of design decisions, some of which made perfect sense in 1974 and create real hazards today. Understanding the precise contract of each copy function — not just its name or its argument count — is the difference between code that holds up under adversarial input and code that becomes a vulnerability report.
strcpy is fast and honest about what it does. strncpy is widely misunderstood and should be retired from general use. strlcpy offers the clearest contract of the three and, with its inclusion in glibc 2.38, has finally earned its place as the default recommendation for most American development teams working in C.
Know your tools. Know their limits. Write strings that stay where you put them.