on the irresponsibility of pursuing C language reform (was: [PATCH 1/2] man/man3/{mem,strn}*(): SYNOPSIS, STANDARDS: Document these as provided by <memory.h>)
Douglas McIlroy
douglas.mcilroy@dartmouth.edu
Sat Aug 1 12:39:08 GMT 2026
Branden wrote
> I'm sure I don't need to bring to your attention what a mine field
> string/`char` sequence/memory buffer handling has been in C since the
> language's inception.
Yes, this is a property of the language, not a peculiar deficiency of the
<string.h> functions. As I see it, patching up perceived deficiencies of the
functions adds complexity to the language definition and to the task of
code-reading, with little real benefit.
Doug
On Fri, Jul 31, 2026 at 7:45 PM Alejandro Colomar <alx@kernel.org> wrote:
>
> Hi Joseph,
>
> > Date: 2026-07-31 23:11:56+0000
> > From: Joseph Myers <josmyers@redhat.com>
> >
> > On Sat, 1 Aug 2026, Alejandro Colomar wrote:
> >
> > > > In other words, they were in that header for 6 years, and it's been
> > > > implicitly obsolescent by virtue of the standard choice for the 37 years
> > > > since then.
> > >
> > > Yes. And I'm trying to revert the implicit obsolescence. Obsolescence
> > > isn't a one-way process. Sometimes, evidence shows up, and the obsolete
> > > feature must come back for $reasons.
> >
> > When it's been obsolescent for 37 years, bringing back a header with the
> > same name is just going to confuse people with 37 years of past
> > information saying it's obsolescent (normally if one source says "use X"
> > and another says "X is obsolescent", you can reliably assume that "X is
> > obsolescent" is the more recent information, even without 37 years of
> > history involved).
>
> This is highly speculative.
>
> If the official documentation says use <memory.h>, use <memory.h>.
> Moreover, I've never seen anything saying <memory.h> is obsolescent.
> It's been only implicitly obsolescent, and most programmers don't even
> know <memory.h> exists at all. FWIW, I've sometimes seen it in the
> system headers, and eventually wondered why it was there, but nothing
> else. Thus, there's no contradicting information about it.
>
> > Any reasonable change there would involve a new
> > header, say <strnpad.h> for strncpy and strncat,
>
> I think that misses that strn*() are still relatively misunderstood.
> It's only because they don't write that they aren't as dangerous.
>
> But we would be much better with strprefix/suffix() and other related
> functions in <string.h> to even further remove uses of strn*()
> functions.
>
> Moving the entirety of strn*() might be less urgent/important than
> moving strncpy/cat(), but it'd still be good to move them. They are all
> about handling [[gnu::nonstring]]s.
>
> > rather than resurrecting
> > a very old one.
>
> I think the very old one is much better, because it can be used by
> virtually all programs already, since it already is there in all systems
> that matter, and already provides the functions were moving there.
> Apart from documentation, there's no real change.
>
> On the other hand, if we were adding a new header, it'd have to be
> wrapped in #if __has_include(), and wouldn't be used until 10 years from
> now or so.
>
> > > The solution of moving both mem*() and strn*() to <memory.h> and leaving
> > > just str*() in <string.h> is a consistent one, because <string.h> then
> > > remains strictly for string APIs, and <memory.h> is for the rest of byte
> > > handling.
> >
> > It's inconsistent with how people have understood C ever since it was
> > standardized. Changing the header memcpy is in is just as ridiculous at
> > this point as the proposal there once was to obsolesce NULL.
>
> I disagree. Obsolescing NULL is a deeply breaking change.
> Standardizing an existing header file is just like standardizing an
> existing function. Nobody is saying <string.h> won't provide memcpy(3).
> My proposal to C2y says that <string.h> includes <memory.h>, so nothing
> changes. All of the existing understanding remains valid (although less
> preferred).
>
> > > It wouldn't be reasonable to move strn*() to <memory.h>, and then leave
> > > mem*() in <string.h>, of course.
> > >
> > > Similarly, it wouldn't be reasonable to more strncpy/cat() to <memory.h>
> > > and leave the rest of strn*() and all of mem*() in <string.h>.
> >
> > On the contrary, it's only the functions for null-padded fixed-width
> > buffers that are niche functions causing confusion, compared to all the
> > rest of the functions in <string.h> for which it's a very well-established
> > and well-understood location. Some others like memccpy are *obscure*, but
> > not confusing in the same way.
>
> Did you read my alx-0096 paper? It shows how n2349 --which introduced
> memccpy(3) in C23-- is full of UB in examples introduced as
>
> To avoid the risk of buffer overflow, the appropriate bound
> needs to be determined for each call and provided as an
> argument.
>
> If memccpy(3) was standardized for reducing buffer overflows (which is
> implied, but not very explicitly said in the paper), it's ironic that
> the examples of how it's supposed to be used invoke UB. If the author
> of the paper can't avoid UB, you can guess it's a bomb as bad as
> strncpy(3) once was. And I'm not saying that memccpy(3) is bad, not
> that strncpy(3) is bad. I use strncpy(3) and know it's fine, and have
> been recently shown that memccpy(3) is actually good for implementing
> fgets(3). But it's not a function for copying strings, and n2349 is the
> ultimate proof. It is just as bad as strncpy(3) if misused for copying
> strings.
>
> I'll paste here part of alx-0096, which clarifies how n2349 invokes UB.
>
> Reading N2349 further, one finds an example of copying with
> truncation:
>
> char *p = memccpy (d, s1, '\0', dsize);
> dsize -= (p - d - 1);
> memccpy (p - 1, s2, '\0', dsize);
>
> This is more prone to bugs than the case above, and more than
> strncpy(3). Anyone suggesting to use this to improve safety
> compared to strncpy(3), please, please, explain to me how they
> think this can be safe in any way.
>
> In fact, the code above is completely bogus, because if the
> string is truncated, p will be NULL, and it invokes UB in
> line 2. See how it was predictably prone to bugs? :)
>
> At the bottom of the N2349 paper, there's a more correct --and
> also more worrying-- example of how memccpy(3) could be used for
> copying strings with truncation. This shows how terrible
> memccpy(3) is for copying strings:
>
> char *p = memccpy (d, s1, '\0', dsize);
> if (p) {
> --p;
> p = memccpy (p, "/", '\0', dsize - (p - d));
> if (p) {
> --p;
> p = memccpy (p, s2, '\0', dsize - (p - d));
> }
> }
> if (!p)
> d[dsize - 1] = '\0';
>
> I think I don't need to explain what can go wrong in such
> unreadable, brittle, and complex code.
>
> ...
>
> Using a more suitable function --similar to POSIX's stpcpy(3)--,
> this could be written much more safely:
>
> char *p = d;
> char *e = d + dsize;
> p = stpecpy(p, e, s1);
> p = stpecpy(p, e, "/");
> p = stpecpy(p, e, s2);
> if (p == NULL)
> goto trunc; // The string was truncated
>
> Here's how stpecpy() can be implemented for this:
>
> char *
> stpecpy(char *dst, const char *end, const char *restrict src)
> {
> ssize_t dlen;
>
> if (dst == NULL)
> return NULL;
>
> dlen = strtcpy(dst, src, end - dst);
> if (dlen == -1)
> return NULL;
>
> return dst + dlen;
> }
>
>
> Cheers,
> Alex
>
> --
> <https://www.alejandro-colomar.es>
More information about the Libc-alpha
mailing list