3 ms·
> In the case where src fits in dst, it will return a pointer past the NUL byte it placed; otherwise it returns NULL to indicate a truncation. It is amazing to
by saurik 5y ago
> In the case where src fits in dst, it will return a pointer past the NUL byte it placed; otherwise it returns NULL to indicate a truncation.
It is amazing to me how personal these preferences are ;P... like, I'd be much happier with an API that always returns the location of the NUL byte on success; and, if the string gets truncated, then it instead returns dst+len (the address of the byte past the end of the buffer). This allows for chained constructions that provide efficient strcat-style semantics with easy error propagation, such as this example which concatenates three strings (which I honestly hope I got right... I'm giving reasonable odds to Saagar telling me I've coded a buffer overflow by accident somewhere ;P):
char buf[X]; // for any X, even 0!
char *cur = buf;
char *end = buf + sizeof(buf);
cur = strjcpy(cur, str1, end - cur);
cur = strjcpy(cur, str2, end - cur);
cur = strjcpy(cur, str3, end - cur);
if (cur == end) goto fail;
- kolbusa 5y agoDid you mean to check for (cur > end)?
- saurik 5y agoNo: if the function succeeds, the address of the NUL byte will always be !=end (as it must be inside of the buffer, which end is not); whereas, in the case of string truncation, cur will be equal to end, as the function returns dst+len (which is end); and, if that error had "already" happened during a previous call, it will get propagated through the next call as end-cur will be 0, causing the next call to immediately fail (even for a 0-length string, which is an important corner-case) and return cur+0 (which is still end).
- saagarjha 5y agoNot seeing anything wrong, but it’s C so who knows ;) Thankfully, the primitive I (and memccpy) provide makes writing your wrapper easy and efficient, as opposed to all the other functions which don’t compose at all. (From my phone) I think this might work? char *strjcpy(char *dst, const char *src, size_t len) { char *result = strxcpy(dst, src, len); return result ? --result : result + len; }
- saurik 5y agoFWIW, part of the fun of strjcpy is that it is also much easier to write than strxcpy (and watch as I go further and further out on a limb with sketchy C that is likely wrong, lol... I did test it, at least! ;P): char *strjcpy(char *restrict dst, const char *restrict src, size_t len) { for (;; ++dst, --len) if (!len || !(*dst = *src++)) return dst; } (edit) Oh, I have an even cuter implementation (which might look "too clever" but actually demonstrates something important about the function)! Essentially, what makes strjcpy so "pure" is that the NULL return is really a "special case" in strxcpy that you're having to "undo" in that wrapper, whereas the semantics of strjcpy--which may sound a bit weird--are mapping directly to what naturally terminates the loop: running out of space on one of the two inputs. This purity then gets taken advantage of by the caller to get such easy call chaining and error propagation, as one of these loops can "continue through" into the next loop without any adaptation logic. char *strjcpy(char *restrict dst, const char *restrict src, size_t len) { for (; len && (*dst = *src++); ++dst, --len); return dst; } (edit) Ok: one difference is that this implementation of strjcpy (this isn't intrinsic to the return value surface I described: just to the gloriously simple versions in this comment; your adapted version, for example, is "fine") doesn't put a NUL byte in case of truncation... though, I personally am not at all sold on doing that: I want to firmly fail the operation, rather than try to "use" the truncated data :/. Adding that special case would still result in strjcpy being simpler than strxcpy (and doesn't break its semantics advantages: just make sure to return the address of dst+len, not that extra NUL), but it isn't quite so amazingly simpler at that point ;P.
- FabHK 5y ago> doesn't put a NUL byte in case of truncation which was an explicit design goal for the author of the article.
- saurik 5y agoWhich is the reason I wrote that paragraph... right? ;P I could repeat the content of that paragraph--which explicitly states... no no no: wait, you're tricking me!--but instead I will use this opportunity (as I had run out of edit time on my comment a while back) to connect it to the original thought of how personal some of these thoughts are and then to provide the trivial patch that adds this functionality to my "elegant" implementation of strjcpy without making it as complicated as strxcpy: [redacted, as actually, omg... I immediately noticed it didn't work! Saagar's implementation of my function does have that property, of course. fixing it for real requires two of statements, one before the loop and the other after it, which is demoralizing as strxcpy arguably asked for semantics which were impossible to do well for 0-sized buffers because of this termination step] If you make the loop more complicated (more like my first copy) you can avoid the duplicate comparison, but I continue to find the elegant second copy much more elucidating, as this demonstrates visceral how the termination is a duplication of effort when chaining, as you are effectively concatenation the loops but then tacking on what should be a single termination step at the end into every intermediate loop. Really, you should just not... ooo: almost repeated that previous paragraph again ;P.
- deleted 5y ago[deleted]