Why strcpy is still in the standard library
Everyone tells you not to use strcpy. The honest history of its replacements is messier than the slogan suggests, and explains why the function won't go away.
"Don't use strcpy" is one of those slogans that everyone repeats and almost nobody questions. It's good advice as far as it goes. The reasons it doesn't go further than a slogan, and why glibc still ships strcpy in 2026, make for a more interesting story than the warning lets on.
What strcpy actually does
char *strcpy(char *dst, const char *src);Walk src byte by byte, write each byte into dst, stop when you hit a '\0' and write that too. Return dst. The function takes no length parameter for the destination, because in 1972 nobody had thought to ask. If dst is shorter than src is long, you write past the end of dst, corrupting whatever lives next to it. On a stack-allocated buffer, the thing next to it is often the saved return address. That's the whole pitch for the "buffer overflow exploit" that defined two decades of security research.
strncpy is not the safer version
The first attempt at a fix, dating to 7th Edition Unix, was strncpy:
char *strncpy(char *dst, const char *src, size_t n);It looks like a bounded strcpy. It is not. Two surprises:
- If
srcis shorter thann,strncpywrites'\0'bytes to fill the rest ofdstup ton. You pay for zero-padding you didn't ask for. - If
srcis longer than or equal ton,strncpydoes not null-terminatedst. The output is a non-string, and the next thing that callsstrlenon it walks off the end into whatever's adjacent.
strncpy was designed for fixed-size on-disk records (think the d_name field in directory entries on early Unix), where neither padding nor missing terminators were bugs. As a strcpy replacement it's worse than the original: programmers think they're being safe and in fact have introduced a more subtle bug.
strlcpy and the OpenBSD answer
The BSDs introduced strlcpy in 1998:
size_t strlcpy(char *dst, const char *src, size_t dstsize);It always null-terminates (truncating src if necessary), it doesn't pad, and it returns the length of src so the caller can detect truncation by comparing the return value against dstsize. As a drop-in replacement for strcpy it's about as close to right as you can get without changing the language.
Glibc has refused to add it. The story is documented in mailing-list threads going back to 2000. Ulrich Drepper argued, with some force, that strlcpy encourages truncation as a default behaviour, that silent truncation is its own bug class, and that programmers ought to be checking lengths and allocating correctly rather than papering over the issue. He had a point. He also kept refusing for two decades while every major BSD, every major embedded libc, and eventually the Linux kernel itself shipped strlcpy. Glibc finally added it in version 2.38 (2023), under pressure from users who had reimplemented it locally enough times that the maintainers gave up.
strcpy_s: the C11 answer that nobody uses
C11 added Annex K, an optional set of "bounds-checking" string functions including strcpy_s:
errno_t strcpy_s(char *restrict dst, rsize_t dstmax, const char *restrict src);The idea was Microsoft-led: every dangerous function gets a _s variant that takes an extra size, validates inputs against RSIZE_MAX, and runs a "constraint handler" on failure rather than silently corrupting memory. Annex K is optional, and the only major libc to implement it is Microsoft's. Glibc, musl, FreeBSD, NetBSD, OpenBSD all decline. The reasons are technical (the API is awkward, the runtime constraint handler is global mutable state) and political (the standardisation process was viewed as Microsoft pushing its own existing API into ISO C).
In practice you cannot rely on strcpy_s being present on a system you don't control. That's the deathblow for a portable replacement.
A timeline
Why removing strcpy is harder than it looks
Even if you could agree on a replacement, removing strcpy from a C standard library is a different kind of problem. The standard library is an ABI contract. Programs compiled twenty years ago link against strcpy by name, and a glibc upgrade that dropped the symbol would break every one of them on the next reboot. The function operates as a public surface that other code depends on, which makes it about as easy to delete as a sentence from a constitution.
The realistic path is what's already been happening: the linker emits warnings (gets was deprecated in C99 and removed in C11, but glibc still ships it as a symbol so old binaries keep working), the compiler adds runtime hardening (_FORTIFY_SOURCE substitutes a __strcpy_chk that knows the destination size and aborts on overflow), and new code is steered away by -Wdeprecated-declarations or by linters. The function persists in the symbol table forever; what disappears is the willingness of any reviewer to let it through code review on new code.
A position
If you have to pick one answer to "what should I use instead of strcpy", it's snprintf for general-purpose copying-with-truncation, because it's portable, present everywhere, and gives you formatting for free. strlcpy is fine if you're not on an ancient glibc. strcpy_s is a non-starter unless you're targeting Windows. strncpy should be reserved for what it was actually built for, which is fixed-size record fields, and even then memcpy plus an explicit terminator is usually clearer.
The "never use strcpy" slogan is correct as far as it goes. It just doesn't capture how messy the alternatives are, or why the function it warns against will outlive everyone reading this.
Where this fits
The string-handling history is a small chapter inside a much bigger story about memory safety in C, which Module 14: Memory Safety & Debugging walks through end to end — buffer overflows, use-after-free, the sanitisers that catch them at runtime, and what you can do at the language and tooling level to make the next one harder to write.