Re: alx-0096r1 - string (and nonstring) copying

Alejandro Colomar <[email protected]> Thu, 30 Jul 2026 21:51:16 +0200
Newsgroups org.kernel.vger.linux-man
Message-ID <amupqzN_X57Du4AJ@devuan>
Hi Mark,

> Date: 2026-07-30 10:58:26-0700
> From: Mark Harris <[email protected]>
>
[...]
> > It would be good to know if those systems that have a larger ptrdiff_t
> > are still alive or can be ignored/forced to adapt.
> 
> SDCC (https://sdcc.sourceforge.net/), when building for mcs51
> (-mmcs51) or ds390 (-mds390), uses a 16-bit size_t and 32-bit
> ptrdiff_t.  It appears that Open Watcom C/16 v2
> (https://github.com/open-watcom/open-watcom-v2) with the huge memory
> model also uses a 16-bit size_t and a 32-bit ptrdiff_t, although I
> haven't verified that one.  Both compilers have active development and
> support some of the new features in C23, but do not have full C23 or
> POSIX support.  I don't know about any others with ptrdiff_t larger
> than size_t.

Thanks; I guess that can't be done.

[...]
> > > https://github.com/illumos/illumos-gate/blob/4b44494cae63d4bf0c7d3f34828503e68d1c0e69/usr/src/lib/libc/port/stdio/fgets.c#L44
> >
> > Thanks!
> >
> > Let's now consider an alternative world where there was a function with
> > semantics similar to memccpy(3) with one difference: it wouldn't copy
> > the delimiter.  Let's call such a function memcpyc().  Here's a naive
> > implementation of memccpy(3):
> >
> >         void *
> >         memccpy(void *dst, const void *src, int c, size_t n)
> >         {
> >                 unsigned char  *d = dst;
> >                 unsigned char  *s = src;
> >
> >                 for (size_t i = 0; i < n; i++) {
> >                         *d++ = *s++;
> >                         if (*s == c)
> >                                 return d;
> >                 }
> >                 return NULL;
> >         }
> >
> > Here's a naive implementation of the hypothetical memcpyc():
> >
> >         void *
> >         memcpyc(void *dst, const void *src, int c, size_t n)
> >         {
> >                 unsigned char  *d = dst;
> >                 unsigned char  *s = src;
> >
> >                 for (size_t i = 0; i < n; i++) {
> >                         if (*s == c)
> >                                 return d;
> >                         *d++ = *s++;
> >                 }
> >                 return NULL;
> >         }
> >
> > (I've written both of the above quickly, without compiling nor testing;
> >  I might have made a mistake.)
> >
> > With such a function, the fgets(3) implementation from Illumos gate
> > would change in this manner:
> >
> >         diff --git i/usr/src/lib/libc/port/stdio/fgets.c w/usr/src/lib/libc/port/stdio/fgets.c
> >         index 9401217410ca..ac1f330ea46e 100644
> >         --- i/usr/src/lib/libc/port/stdio/fgets.c
> >         +++ w/usr/src/lib/libc/port/stdio/fgets.c
> >         @@ -82,9 +82,12 @@ fgets(char *buf, int size, FILE *iop)
> >                                         break;          /* nothing left to read */
> >                         }
> >                         n = (int)(size < iop->_cnt ? size : iop->_cnt);
> >         -               if ((p = memccpy(ptr, (char *)iop->_ptr, '\n',
> >         +               if ((p = memcpyc(ptr, (char *)iop->_ptr, '\n',
> >                             (size_t)n)) != NULL)
> >         +               {
> >         +                       p = stpcpy(p, "\n");
> >                                 n = (int)(p - ptr);
> >         +               }
> >                         ptr += n;
> >                         iop->_cnt -= n;
> >                         iop->_ptr += n;
> >
> > IMO, this wouldn't make it more complex.  However, such a hypothetical
> > memcpyc() would be much more useful elsewhere.  For example, it would
> > simplify the two unique uses of memccpy(3) in FreeBSD:
> >
> >         diff --git i/bin/sh/parser.c w/bin/sh/parser.c
> >         index 0c1b7a91c257..713a35ad8cd8 100644
> >         --- i/bin/sh/parser.c
> >         +++ w/bin/sh/parser.c
> >         @@ -2116,7 +2116,7 @@ getprompt(void *unused __unused)
> >                                 if (fmt[0] != '}') {
> >                                         char *end;
> >
> >         -                               end = memccpy(tfmt, fmt, '}', sizeof(tfmt));
> >         +                               end = memcpyc(tfmt, fmt, '}', sizeof(tfmt));
> >                                         if (end == NULL) {
> >                                                 /*
> >                                                  * Format too long or no '}', so
> >         @@ -2129,7 +2129,7 @@ getprompt(void *unused __unused)
> >                                                 fmt--;
> >                                                 break;
> >                                         }
> >         -                               *--end = '\0'; /* Ignore the copy of '}'. */
> >         +                               *end = '\0'; /* Ignore the copy of '}'. */
> >                                         fmt += end - tfmt;
> >                                 }
> >                                 now = localtime(&(time_t){time(NULL)});
> >         diff --git i/lib/libc/amd64/string/strncat.c w/lib/libc/amd64/string/strncat.c
> >         index 2c63ab50b3c3..4a1e8c0119ac 100644
> >         --- i/lib/libc/amd64/string/strncat.c
> >         +++ w/lib/libc/amd64/string/strncat.c
> >         @@ -19,13 +19,13 @@ strncat(char *dest, const char *src, size_t n)
> >                 char *endptr;
> >
> >                 len = strlen(dest);
> >         -       endptr = __memccpy(dest + len, src, '\0', n);
> >         +       endptr = memcpyc(dest + len, src, '\0', n);
> >
> >                 /* avoid an extra branch */
> >                 if (endptr == NULL)
> >         -               endptr = dest + len + n + 1;
> >         +               endptr = dest + len + n;
> >
> >         -       endptr[-1] = '\0';
> >         +       *endptr = '\0';
> >
> >                 return (dest);
> >          }
> >
> > memccpy(3) is a bad design, which forces complex code, prone to
> > off-by-one bugs.
> 
> It seems like you've already made up your mind that memccpy() is bad
> and are now just looking to justify that decision.

Not necessarily.  I can be convinced with good arguments.  :-)

> Your modification of fgets() is obviously more complex; it adds a
> whole additional function call, and instead of copying all of the
> bytes at once from a single source it does two copies from two
> different sources, specifying the delimiter twice making it more
> error-prone and difficult to change.  Both fgets() and strncat() need
> the delimiter in the destination buffer, and the one place that
> doesn't is only "simplified" by removing a single "--"; certainly not
> worth a whole new function.  And if simplicity or eliminating the
> +/-1s was important to the author of that strncat() implementation,
> they could trivially achieve those goals with the existing function:
> 
>     size_t len = strlen(dest);
>     if (__memccpy(dest + len, src, '\0', n) == NULL)
>         dest[len+n] = '\0';
>     return dest;
> 
> Changing that to use memcpyc() would have made it more complex.

Agreed; the above is a good implementation of strncat(3), and better
than with memcpyc().  Thanks!

> I think that copying the delimiter makes sense.  For example, you
> should be able to use this function directly in a loop, e.g. to
> process '\n'-delimited lines.  If it stopped before the delimiter it
> would need extra code to skip over the delimiter before the next
> iteration to avoid getting stuck there.  But if the final line didn't
> have a '\n' at the end it would have to be sure not to skip over it to
> avoid advancing past the end of the buffer.

Agreed; there are use cases where memccpy(3) is preferrable.  You've
convinced me.  :)

> Even if this hypothetical memcpyc() function was slightly better I
> don't see how that matters.  memccpy() has been around for decades, is
> already in ISO C, is already in POSIX, has already been implemented in
> numerous C libraries even before ISO C existed, and is in use in
> non-buggy ways, so I don't see it going away in the foreseeable
> future.  The purpose of the standard is to allow for confident
> reliance on a defined base set of features, and removing functions
> from that set that are in use and have no fundamental flaws erodes
> that confidence.  memcpyc() has no existing uses, and also little
> expected use, so it would be difficult to imagine there being enough
> support to add it to ISO C.

Agreed.


Have a lovely night!
Alex

> 
> 
>  - Mark
> 

-- 
<https://www.alejandro-colomar.es>
signature.asc (application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEES7Jt9u9GbmlWADAi64mZXMKQwqkFAmprqy4ACgkQ64mZXMKQ
wqkR4Q//V+L4iPHPGWxO/oir25KMdSyN3iAxZyt0EcXiN1lPyQMjlGiwMuAEMxKi
2RJyAOBoKe4E6wWwKkvAHi/OviotsH/f5iLMyg/4WY4+GIeerzbdRY75a9bUWpag
Y2DsmY7c/EeXJ2CPi+U6I+akXl1dl1sDWjJgwVlsIq2pfPZzoAkYiyYDu4izCTqo
f4HXZfTiNBv0+Aak3UdSfujj8HFyTKHyBs/JpDcZJ07/pg83/iZNeroSYcUf4bmr
EP9kD2gdszOn6E2sEYrU7hSMXoe3GR75GzpL+jkvLS+zVF1Mit2Vp8Kr4mDQ3mmz
qVBO12vA/VX/5/c/wk3gB6kz17R+EHR95++Xpo5yizYaM56JKDCYqrW6gFwsRFOj
tpM1qXEYohMZRoNrAzTwiq6kva+7LxTOf2eK1w/iVLo56osnsNo3tuz7P0Rm/pC4
3SScFtyyZO9jwN92jDCZoF7egWRnILUaH0322FUDs71ekgYMp2o58aW7gxLc3rv4
1i8AeQbxuDip0ZT//Cye2Kn0WfBeliSoE54genE1+J92D//T4NG59GiKhFeenMTW
73YmMbfjBACzwpmFDTZdmeYkAaQxwqnJrKiM7RhQM8nCJQtCP3H2pAlsRNEBGlMS
9x8y+eb0iew/D8Dd1qmdetRNTAUTUoDVAp8KqYvghHXi+owChCQ=
=GGnd
-----END PGP SIGNATURE-----