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 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.
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.
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.
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.
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.
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.
> 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).
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".