Re: strncpy clarify result may not be null terminated

Jonny Grant
2023-11-18T23:03:23Z

On 18/11/2023 10:12, Alejandro Colomar wrote:
> Hi Jonny,
>
> On Fri, Nov 17, 2023 at 09:57:39PM +0000, Jonny Grant wrote:
>>> - strlcpy(3)'s first order growth corresponds to strlen(src). That's
>>> due to returning strlen(src), which proves to be a poor API.
>>>
>>> - strncpy(3)'s first order growth corresponds to sizeof(dst). That's
>>> of course due to the zeroing. If sizeof(dst) is kept very small, you
>>> could live with it. When the size grows to more or less 4 KiB, this
>>> drag becomes meaningful.
>>>
>>> - strnlen(3)+*cpy() first order growth corresponds to
>>> strnlen(src, sizeof(dst)), which is the fastest order of growth
>>> you can get from a truncating string-copying function (except if you
>>> keep track of your slen manually and call directly memcpy(3)).
>>
>> That's a really good point, keeping track of the length (and buffer size) and then just using memcpy.
>> The copy time should be closer to the number of bytes read and written.
>
> Actually, the performance of memcpy(3) should also be on the order of
> strnlen(src, sizeof(dst)), so it should always take similar times
> compared to strnlen(3)+*cpy(). It is only that it will always be
> slightly faster due to avoiding a second read, but it will only be a %.
> Nothing like 10x, which can easily happen with strlcpy(3) or strncpy(3).
>
>>> Of course, first order of growth ignores second order of growth and so
>>> on, which for small inputs can be important. That is, O(x^3) is bigger
>>> than O(x^2), but x3 + x2 can be smaller than 5*x2 for small x.
>>>
>>>>
>>>> As Paul mentioned, strlcpy is a poor choice for processing strings.\
>>>> Could rely on their guidance as they already measured.
>>>> https://www.gnu.org/software/libc/manual/html_node/Truncating-Strings.html
>>>
>>> Indeed. I've added important notices in BUGS about it, and recommended
>>> against
>>
>> Saw glibc have (11) functions listed as a poor choice for string processing
>
> They list many functions as poor choices for string processing. The
> problem is that they list those functions for string processing. I went
> a bit further and de-listed some: We don't list strncpy(3) or strncat(3)
> as functions that process strings, but rather as something else. And
> they are actually good functions for processing that something else.
>
> The problem with strlcpy(3) is that it's a function that is designed to
> process strings, and being bad at processing strings makes it a bad
> function period.
>
>>>> Maybe the strlcpy API is easier, safer for programmers; but the
>>>> compiler can't figure out that the programmer already knew src string
>>>> length. So the strlcpy does a strlen() and wastes time reading over
>>>> memory. If the src length is known, can just memcpy.
>>>
>>> I've written strtcpy(3) as an alternative to strlcpy(3) that doesn't
>>> suffer its problems. It should be even safer and easier to use, and its
>>> first order of growth is better. I'll send a patch for review in a
>>> moment.
>>
>> I did take a look at strtcpy but it calls strnlen(), reading over memory.
>
> That's just a few % slower than memcpy(3). Don't expect memcpy(3) to be
> much faster than this. strtcpy() reads twice writes once; memcpy(3)
> reads once writes once. So you can expect memcpy(3) to be constantly
> 33% faster (very roughly).

Probably there are benchmarks, measurements comparing those functions which use strnlen() to those that just do memcpy()? Would be interesting to hear what the time is to do those reads & writes, or just do writes.

> If you implement you own strtcpy() in assembly, maybe you can get
> something that's in the single-digit % slower than memcpy(3), similar to
> strcpy(3).
>
>>>> When I've benchmarked things, reducing the memory accesses for read,
>>>> write boosted performance, also looked at the cycles taken, of course
>>>> cache and alignment all play a part too.
>>>
>>> If one wants to micro-optimize for their use case, its none of my
>>> business. I provide a function that should be safe and relatively fast
>>> for all use cases, which libc doesn't.
>>>
>>>> Maybe could suggest in your man page programmers should keep track of
>>>> the src size ? - to save the cost of the strlen().
>>>
>>> No. Optimizations are not my business. Writing good APIs should make
>>> these optimizations low value so that they aren't done, except for the
>>> most performance-critical programs.
>>>
>>> The problem comes when libc doesn't provide anything usable, and the
>>> user has no guidance on where to start. Then, programmers start being
>>> clever, usually too clever. That's why I think the man-pages should go
>>> ahead and write wrapper functions such as strtcpy() and stpecpy()
>>> aound libc functions; these wrappers should provide a fast and safe
>>> starting point for most programs.
>>>
>>> It's true that memcpy(3) is the fastest function one can use, but it
>>> requires the programmer to be rather careful with the lengths of the
>>> strings. I don't think keeping track of all those little details is
>>> what the common programmer should do.
>>
>> That's true, high-performance users probably create their own bespoke solutions.
>> strtcpy probably takes the src size?
>
> No. strtcpy() takes the dst size.
>
> ssize_t
> strtcpy(char dst[restrict dsize], const char *restrict src, size_t dsize);
>
> This function doesn't care about the src size. It requires that it's
> either a string, or a character array larger than dst. In both cases,
> it means that the internal calculation of slen = strnlen(src, dsize)
> will never overrun the buffer, while costing only a small time.

Ok I see, I would rather use something that allowed the src_len to be specified, to save that strnlen() cost.

Kind regards
Jonny