Re: alx-0096r1 - string (and nonstring) copying
Alejandro Colomar <[email protected]> Thu, 30 Jul 2026 22:02:08 +0200
| Newsgroups | org.kernel.vger.linux-man |
|---|---|
| Message-ID | <amus_s5gwmZ69UU7@devuan> |
> Date: 2026-07-30 21:51:16+0200 > From: Alejandro Colomar <[email protected]> > > 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 FWIW, that additional function call is optimized out by GCC even on low optimization levels, since it's a two-byte copy. You could perfectly use a single-byte manual copy: 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++ = '\n'; n = (int)(p - ptr); + } ptr += n; iop->_cnt -= n; iop->_ptr += n; Nevertheless, with your examples of fgets(3) and strncat(3), I'm now convinced that memccpy(3) is good for this niche use case. > > 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. This is true, though. Cheers, Alex -- <https://www.alejandro-colomar.es>
signature.asc
(application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEES7Jt9u9GbmlWADAi64mZXMKQwqkFAmprrboACgkQ64mZXMKQ wqlvpxAAicF2FMwaeEoZrU6YVBN1i+BLRYBE+bPP4Md/2ibrXJLvzq3pE1gAalKM qEsR/QNB7hRhCfpvCf90bxJFc3+TOh80SK+HL4kHdNZgYVLIOLyFqPrK03neel0C 0J0SQbmLcy+ZYG8r3BBI30TKCvzymAFCb9s5YA5bcYPGUFS3yDV+0+GohSlFGPdf CM+egmF02WaoFG7loc0SBk6nkk/RvAaR1e9S7CXp1n18srbH7FTckPkaRPM1u5fW Zfz0koBf60WLayZdYeklB9WVlIfXqqToMC7W7UieKLjct1/h/R9eH0ACybxnXxLM CHy45gRiqqgtxpq89RDqDs2/MJA/LH0YD/T9su18wQJCtIUu2t1LdwwkjDL2wkbD 1dSEw0y2JoeL7xEPzzmiWY3myWjALdeFcnXonzua4Mb4BtCEzIpTwHD3n3cfNPog 8UVfh4eN6JwopuZuLxZFEHytL/v8EI6nSC4kkjuBLBoqOp5kQiKiiO1pNAXklO/e jPRw2hQC0JpVdoFTvKwNgOhyDd3716wSRJxYmUzX0ynKjW80r8ECLLgwK4B8bHrs XtUuF+J3BRirCCMgXpzLJWJ2tB0kdzaPGRqfrh10GfdbIJtAxLR/pWES+RPBiXqj ZmF4byjWmjtqiryaTFb1SDBhWt/NbFA0d0h+LF7yjDyfmrzR9Pk= =Jzko -----END PGP SIGNATURE-----