Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Note that strncpy() is not intended for safety. The purpose of strncpy() is to write to fixed size data structures such as part of the filesystem where you don't want to store NUL termination on strings.

Like 1980s Internet protocol features the rationale for weird things in C is more often "That's how Unix works" than "This is actually a clever safety feature".



> write to fixed size data structures such as part of the filesystem where you don't want to store NUL termination on strings

... AND where you want to pad the remaining space with zero bytes, so that you don't leak uninitialized memory onto the disk, or network.

The null byte padding behavior of strncpy makes it clear what the intended use was.

Also, the way C initializes character arrays from literals has strncpy-like behavior, because the entire aggregate is initialized, so the extra bytes are all zero:

   char a[4] = "a";      // like strncpy(a, "a", 4);
   char b[4] = "abcd";   // like strncpy(a, "abcd", 4);
the compiler could literally emit strncpy calls to do these initializations, so we might say that strncpy is a primitive that is directly relevant for run-time support for a C declaration feature.


Is the original intent of strncpy() germane to the GPs comment? Explicitly stating the max length to copy is an effective tool for avoiding buffer overruns, regardless of whether the designers imagined that important use case.


But the thing it does (fill out a fixed sized buffer without caring about NUL-termination) is not at all what you'd want from a safety feature.

If you look at this function assuming it's a safety feature, that's a huge surprise, and indeed if you were skimming you might miss what it does because (in the context of "it's a safety feature") this is an insane choice. "Why would you do that?". Well, because it's not a safety feature.

The perf cost isn't what you'd expect from a safety feature either. Suppose we have a 1MB buffer, and we strncpy "THIS" into it using n = 1024. That's just four bytes right? Nope. strncpy() will write "THIS" and then 1020 zero bytes.


Except strncpy is broken for C strings, because it doesn't guarantee nul termination. So if you forget to force a termination on every use, you get buffer overruns.

Not only that, but (because of its actual purpose) it also fills the buffer with nuls, which is a complete waste of resources.

So yes, the original intent of strncpy() germane to the GPs comment, because it makes strncpy actively dangerous and complete shit when working with C strings.


The issue is that strncpy() isn't a str___() function, despite its name. It's a 0x00-padded memcpy.


The output side of strncpy() might not be a str___() function, but AFAICS the input side of strncpy() is clearly a str___() function, since it stops reading (but not writing) at the first NUL byte.


But it's not an str* function, it's an strn* function. And most (though not all, that would be too easy) work on fixed-size (hence the n) nul-padded strings.


No, it isn't a string function of any kind. "A string is a contiguous sequence of characters terminated by and including the first null character." § 7.1.1.

Calling a bespoke byte-sequence data structure a "string" is inaccurate. Treating strncpy() as a string function is erroneous and can easily lead to memory corruption.


[flagged]


> Have you considered giving reading comprehension a try?

Don't do this.


Then don’t demand it by wilfully misunderstanding comments in order to “well actually” them.


Do we have a source for what the intended purpose is? I think you speak well to the effective purpose, but I'm not sure if it was that clear when it was introduced.


> I think you speak well to the effective purpose, but I'm not sure if it was that clear when it was introduced.

It was completely clear, and can easily be inferred from its specified behaviour.

It's just completely useless nowadays, because its purpose is essentially obsolete, because the data type it works with is almost never used anymore.

strncpy works with fixed-size nul-padded fields as you'd find in e.g. mainframe-type software. That is why it:

- fills the destination buffer with NULs if the source is shorter

- does not nul-terminate if the source is the same size or longer than the destination

strncpy is essentially equivalent to zero-ing a buffer of size `n` then copying the first `n` bytes of src (up to the first nul) in the target


That's your understanding now. Fine, but not evidence of what someone else thought about this some decades ago.

(I don't use strncpy and don't defend its functionality, just want to know what was intended when it was introduced.)


I found an actual quote of a source which verifies the intention for fixed-length fields: https://softwareengineering.stackexchange.com/a/438090


On early Unix systems, the directory structure was a simple 16-byte record; 14 bytes for the name, and 2 bytes for the inode. [1] strncpy() was used to simply record the file name into this structure.

[1] "UNIX Implementation" by Ken Thompson, _The Bell System Technical Journal_, July-August 1978, Vol 57, No 6, Part 2, pg 1942.


Many protocols still relevant today make use of that structure, it still has widespread use.


For the average protocol you're going to init the entire message then set into it, you don't need to fully zero fields.

This is mostly relevant to write into existing memory or memory-mapped records.


Sometimes yes, but you must also not null-terminate the strings in those cases.

And on embedded devices you are often memory constrained so you might reuse an existing structure.



Doesn't say anything about the intention as understood when it was introduced


Get strlcpy and strlcat from OpenBSD.


Any version that still relies on separate parameters is unsafe, no matter what.

Some typo on the buffer limits and the same hazards as always.

Only fix is hardware memory tagging.


C needs to bite the bullet and adopt slices as first-class types, so that they can be optimized on ABI level.


How would you optimize slices the ABI level? Supporting them in function calling conventions should be easy. But figuring out storage representations I suppose would be a huge can of worms. There are too many ways of encoding slices depending on the use case. It only starts with the choice of a length field type (8, 16, 32, 64 bit. signed or unsigned)? There are also other representations thinkable, like sentinel values (NUL terminator) or more implicit storage of the size. Supporting them all in the compiler is not possible in practice.


"Slice" is, by now, a fairly established term in PL design which implies a tuple of (start, end) or (start, length), so it specifically excludes prefixed length, null termination etc - because experience has shown that slices are the only sane choice.

What I meant by optimization is not treating them same as other structs, but e.g. guaranteeing pass-by-register like other primitive types, spelled out explicitly in the ABI. The choice of length field type would be size_t, obviously.


Like every other systems programming language with a slice like feature, including those that predate C.


Please show, don't tell. Your sibling explained that they meant only function call optimization. And I would say it's debatable that this is an "optimization" since slices would be a new concept that is distinct from structs. I agree though that the obvious naive choice would be to pass them in the same way that structs of { ptr, len } are passed in the ABI.


Infosec people have been showing the C folks for decades, showing alone isn't enough, when people refuse to change their habits.


Multics?


I don’t think Multics did hardware memory tagging.

Systems which do/did include Burroughs Large Systems (now Unisys ClearPath MCP), IBM System/38 and AS/400 and IBM i (the RISC versions of which used PowerPC AS Tagged Memory Extensions), ARM MTE, SPARC ADI, and CHERI/ARM Morello.


Multics didn't need it, because PL/I does bounds checking by default, it has proper string and array data structures.


then why not just use memmove() instead of strncpy() if no-NUL is the goal? not to mention memmove() is overlap-safe.


> then why not just use memmove() instead of strncpy() if no-NUL is the goal?

no-nul is not the goal of strncpy, it's the effect of strncpy.

strncpy is designed to work on fixed-size, nul-padded strings. That's why it fills the destination buffer with nuls if the source is too short, and it doesn't guarantee nul-termination (if the source is exactly the size of the destination).




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: