Efficient string copying and concatenation in C
developers.redhat.com
developers.redhat.com
Unfortunately, I wouldn't call all of it critically useful stuff. For example, "strfry" and "memfrob" are useless beyond toy applications as neither are remotely secure; "l64a" and "a64l" are useless (and possibly even dangerous) because they look like they implement base64 but with a different alphabet (and a much worse API); they have a hilariously large number of random number generators, _none_ of which produce high-quality random numbers for simulation or especially cryptography; etc. etc.
Neither MSVC nor macOS will let you truly statically link a binary (because the system interfaces aren't constant), so it's harder to directly compare, but on macOS "libSystem.dylib" depends on basically everything in /usr/lib/system which is about 5MB of code.
So yeah, I don't think I'd call many real libc libraries "small" by any stretch.
http://www.etalabs.net/compare_libcs.html
For the .a size:
musl: 426k
uClibc: 500k
dietlibc: 120k
If you statically link to these, your final binary will only pull what is actually needed and be smaller than this.
Adding better string functions or CPU-specific optims is nothing compared to that.
musl is mostly so small because it only does the C locale and basic unicode towlower. Nothing like the monstruous glibc unicode tables or internationalized error messages.
[1] for example: https://github.com/websnarf/bstrlib/blob/master/bstrlib.txt
And what value what that be?
... if you're going to say "easier to fit a small implementation into the memory of some microcontroller" - not a sufficient argument. There's always some smaller system with not enough memory, and larger systems with more than enough.
People have a range of technical and aesthetic reasons for hating bloat and C attracts a lot of them.
Off the top of my head:
- Smaller memory footprints
- Easier reimplementation
- Lower attack surface
- Less room for breaking changes
- Less baggage for when we inevitably wish we could deprecate things
Nobody said you need to link the entire library.
Plus - with custom-made libraries, the memory footprint is no smaller.
> Easier reimplementation
Implementation begins with some part of the standard library, and gets completed gradually later on.
> Lower attack surface
Are you really arguing in favor of "everyone roll your own library" as a reduction of attack surface?
> Less room for breaking changes
C's standard library was already broken in various significant ways to begin with (e.g. gets() ...) .
Also, are you really worried about all those breaking changes between C99 and C11?
> Less baggage for when we inevitably wish we could deprecate things
Umm, you do realize things usually get deprecated when they've been replaced by something more relevant, right? In our case - new C library code.
wut?
Granted, using C means developers often implement these operations themselves which introduces the possibility of creating more attack surfaces. But it's less likely that the standard library presents an attack surface when the standard library is tiny.
I've been working with it for nearly two decades, and every year I think more that C programs should be confined to a well-guarded quarantined area with hazard trefoils and a "beware of the leopard" sign.
[1] https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
edit: people reimplementing their "safe" string library isn't something to brag about, but be ashamed of our entire industry for.
A lot of embedded development still have very tight requirements. We're still talking about things discussed in tens or if generous, hundreds of kilobytes.
As a more mainstream example you might have heard of, the Arduino, I think the baseline Arduino has around 32KB available.
Also, in the embedded space, the compilers used are notorious for not having a complete standard library implementation. Lots of things are missing. Thus, developers who care about porting their code to different chips, are cautious about limiting what standard library functions they depend on.
Again, as a more mainstream example you might have heard of, look at Lua, which is implemented in 100% ANSI C. Because they care about Lua truly being portable to everywhere, they try to constrain their dependence on the standard library, and they supply some #ifdefs in luaconf.h to implement or workaround some standard library functions they know to be often missing.
And as a more mainstream example of an incomplete C standard library, look at Android. Bionic is the name of their C standard library. It is missing a lot of things. (And you have to use one of those #ifdefs in luaconf.h to compile Lua successfully on Android to workaround Bionic.)
C++ has a large standard library. This ends up being one of C++'s biggest downfalls: the various libraries don't play nicely with each other, have many different ways of doing the same or similar things, each have complex gotchas--and it's impossible to know them all, so you never really know C++. It's full of dark corners, and the result is bugs, and difficult debugging, and teams that try to enforce only using subsets of the language (and inevitably fail a little). This problem is only getting worse over time. The reason C++'s new features haven't just been backported back into the C standard is that people saw this coming and didn't want it happening. C is already a complex language, but it's still simple enough that you can actually reasonably read the entire C standard library (I have; QED).
A large standard library doesn't have to be this way. Python has a large standard library--this is the "batteries included" of Python. The reason Python can get away with this is that they can deprecate their mistakes. And they are: quite a few modules in the standard library will disappear with the Python 2 sunset. But this is not without downsides--everyone who has been working on a major Python project in the past few years has felt the pain at some point. And C is not Python: it's the backbone of a lot of other applications, so breaking changes are far more costly for C than for Python. C is "once supported, always supported", and for very good reason, so we can't afford to introduce mistakes to the standard.
So, if you don't want the problems of all the cruft in C++'s standard library, and you don't want the deprecations of Python's standard library, your only remaining choice, by process of elimination, is to have a very slim standard library, and make VERY conservative choices about what to add to it.
I think that we should add a new string library to C when strings are a solved problem. But contrary to what people are saying, strings aren't a solved problem. We shouldn't add a string library that doesn't solve internationalization, for example. And in fact, with that problem, the fox is in charge of the henhouse, making the problem worse: we've got people in the Unicode team adding fsking emoji to the standard. Unfortunately, I think we're going to have to wait until Unicode collapses under its own bad decisions and is replaced by a better standard, and that standard becomes mature, before committing to that proven solution. Otherwise we end up with ICU-related crap into the "once supported, always supported" C standard library.
Is it that large? i.e. is it larger than, say, Java's ?
> it's impossible to know them all, so you never really know C++
This is semantic quibbling. You know a language more if you know its commonly-used libraries better - whether they're in the standard or not.
> the various libraries don't play nicely with each other
They may not be in perfect synch, but they play ok-ish-ly with each other - as far as I can tell.
However, not playing nice is much much more of a problem with non-standard libraries - in various languages. I'm sure C programmers have that experience with a vengeance... say, everybody using different implementations of basic data structures like queues and lists and hash tables and so on.
> It's full of dark corners
Yes, yes it is. And they're pretty scary... however, that's not really because of the library size. Also, those dark corners exist, unexplored, for C libraries; it is arguably better that they be mapped once and for all, at least some of them which are somewhat-frequently stumbled upon.
...
More generally - the issue you're describing seems to be more with the combination of "iron-clad backwards compatibility" and a larger standard library:
- C has the former, not the latter - Python has the latter, not the former
C++ has the former (upto minor points like the `auto` keyword), as well as the latter, but with warts.
The warts are there, that's true, but they're not so bad. And use of the standard library does pay off very nicely - often removing warts in your own code.
Large, yes. Larger than Java's? I don't know.
It's larger than C's, that's for sure.
> This is semantic quibbling. You know a language more if you know its commonly-used libraries better - whether they're in the standard or not.
If it's in the standard library you better bet it gets used, even if only by your dependencies.
> I'm sure C programmers have that experience with a vengeance... say, everybody using different implementations of basic data structures like queues and lists and hash tables and so on.
Eh, it's not too bad in C. Libraries tend not to force you to use their data structures too much, and if they do, you just don't use that library. Some, you use their data structures because that's what you included the library to get. It's certainly a problem, but dependency management is easier than having dependencies you can't make go away.
> The warts are there, that's true, but they're not so bad. And use of the standard library does pay off very nicely - often removing warts in your own code.
So use C++, if you like it.
My post wasn't an attack on C++. C++ has its upsides. I only described the downsides of C++ because I was explaining what C is trying to avoid.
There are a lot of positions this would be a great indicator of whether or not the candidate could do the job.
If low level programming is involved the candidate better be able to handle this. Just cause you don't have to solve this problem every doesn't mean you're not going to need those skills for systems programming, writing drivers, embedded programming, etc..
I suspect this would have an extremely high weed out rate actually. A very large % of so called software engineers would fail this, certainly anyone who did all their learning in a dynamic language with automatic garbage collection and who had not branched out into lower level programming.
It's a dumb question if the job position is for a Javascript UI dev of course.. it's not a good question for all positions.
I would also say it's a travesty for any school to award anyone a bachelor's degree in computer science to someone who would fail this question. I've interviewed a large # of candidates with master's degrees who would fail this. Thankfully most of those candidates didn't have a bachelors in computer science + a masters in computer science from a highly ranked US institution.
Also someone who groks this problem and solves it is way less likely to write memory leaks into code in a garbage collected language.
http://www.kylheku.com/cgit/txr/tree/lib.c?id=txr-225#n3897
(The mysterious wini, wref and auto_str macros here allow a C character array on the stack to be a Lisp value with a type tag, recognized and ignored by the garbage collector.)
While FizzBuzz has become notorious for weeding out total incompetence [1], there are some variations of the problem that are unexpectedly nuanced.
1. https://thedailywtf.com/articles/The-Fizz-Buzz-from-Outer-Sp...
If I had to do something like that in a real program I'd very much overalloc a few bytes (because why not? If those small allocations really become a problem in your app you probably want to use a custom allocation scheme anyway) and then just snprintf the result into it. I'd go even further and say that code that would micro-optimize this to save a couple of bytes at the cost of maintainability shouldn't pass a code review IMO.
It's why call it "stupid", "insidious", but "somewhat relevant", as the parent comment proposes a better stdlib in C to handle strings.
What I was getting at is that in virtual memory the heap is always contiguous (even if it isn't in physical memory, but we only need it to be contiguous in virtual memory). So one can guarantee that the solution will never require copying data if the program exclusively uses the stack, and the resultant string is the only piece of data on the heap. You always add contiguous memory so your string can dynamically grow without ever copying to a new buffer. This probably requires allocating memory through the use of OS-specific syscalls to request new memory pages instead of malloc or realloc.
The beauty of the C string is that it is minimal and straightforward: if you have a pointer to it, you know where it ends. And it works with pretty much any OS out there.
(My personal variant of dynamic strings are refcounted, length prefixed and nul-terminated with the pointer pointing to the traditional C string.)
size_t, or uintptr_t [1]. Maybe uint32_t, although given pointer alignment, smaller-than-pointer for size doesn't help all that much.
> The beauty of the C string is that it is minimal and straightforward: if you have a pointer to it, you know where it ends.
And if you screw up adding a null byte, client code will happily keep running through the rest of memory to find it. And if your string has embedded nulls, you're SOL anyways.
> And it works with pretty much any OS out there.
And for safety reasons, the OS generally has to assume a maximum length anyways, otherwise, you tend to have security vulnerabilities.
[1] size_t is not necessarily the same size as uintptr_t. An architecture with a 64-bit base pointer shared across several 32-bit indexes is an interesting idea that could use some more programming exploration.
struct string { char *data; size_t size; size_t alloc_size; };
struct string_view { char *data; size_t size; };
And we have the same design that C++ uses. struct string {
union {
struct {
char *ptr;
size_t capacity;
};
char str[16];
} data;
size_t length;
};
It looks complex, but it’s actually a really nice design because it requires no separate allocation for strings less than 16 bytes long, which is a common case, and the strings are relatively compact. And, it stores a capacity parameter which allows you to know when it’s safe to grow the string without allocating, making it possible to implement efficient repeated concatenation.My personal variant uses 32-bits for length and 32-bits for refcount, which aligns well on most platforms, including 64-bit ones.
Something like:
struct string_header
{
int32_t refcnt;
int32_t length;
// character content starts here
};
Given a char pointer to get to the header you must do: ((struct string_header *)p)[-1]>I
>I
There is no 'I' in standard libraries :)
To clarify this is part of a larger library that includes other memory management machinery, such as autorelease pools, etc.
The good thing about 64 bit lengths is you never need to worry about overflowing the string. The bad thing is no one know what to do, or codes for the possibility of the string overflowing. 8 bits 'should' avoid people making that assumption.
Something anything is better than the total shit design we've been living with.
Exactly like what is returned by strlen... ?
There was a quiet market war some forty years ago about that, and Pascal lost.
But yeah, now we can afford it, the downsides vastly exceed the vanishing cost.
sys/queue.h provides four different linked list implementations: standard singly-linked lists, standard doubly-linked lists, a singly-linked list with a tail pointer (for fast tail insertion) and a doubly-linked list without a head pointer. All come complete with
sys/tree.h provides two types of balanced search trees: splay trees and red-black trees. Both provide ordered dictionaries
Both libraries make quite extensive use of macros, but they are quite reasonably implemented and documented via man-page. I've successfully used them a few times. Shame that, like other nice BSD innovations (e.g. strlcpy/strlcat) they haven't made it into other libcs.
They've been moved to the Xcode Command Line Tools with the rest of the standard headers.
Off-topic, I know, but what's the benefit of this and how does it differ from a doubly-linked list without a tail pointer?
Then compilers wanting to support the "with-batteries" superset standard could just allow their flag that looks like "-std=c90" to be extended like "-std=c90+batteries".
Or you can JUST use D.
Why people always skip enormous work already done decades ago and whine that they miss something and something in C?
What this means is that:
• people who are learning a language, can learn language features "through" examples that rely on the included batteries to demonstrate a point (for example, image support in DrRacket, or the HTTP client in Go), without needing to also learn everything involved in ecosystem package management first;
• people who want to write small, self-contained, yet portable utility programs (e.g. coreutils), can just rely on the language runtime and its presence on basically every OS, rather than declaring package dependencies (= not portable) or statically linking in their own libraries (= not small). The more stuff a language's runtime does, the more such programs become possible to write in said language.
• features in a shared runtime can rely on other things in a shared runtime; and the library ecosystem of a runtime can use the runtime's data-structures as a lingua franca to specify library APIs in terms of. JS libraries return promises because the JS runtime includes promises. Elixir libraries pass around DateTimes because the Elixir runtime specifies a DateTime type. Go libraries take and return slices, maps, and channels because those are things that exist in the runtime. If these runtimes didn't have these things—even if the languages had fancy macro systems that meant that pulling in the relevant library would enable exactly the same syntax—then library support for these would be fragmented, rather than expected. Exactly the way library support for prefix-length strings is in C.
I understand there are benefits to small or no changes whatsoever.
I also understand there are a number of standards (1990, 95, 2011...)
Is it that all the people who would be interested in improving C just naturally migrate to C++ which is getting improvements?
Your example of better strings is good, but why not lists and hashes as language elements?
If not in the language directly, then ok in the library. But speaking of the library, why limit to just a few things, why not batteries fully included. shoot for knuth in the standard library!
Probably because there are many ways to implement data structures, for very specific, and entirely different use cases. C gives you the minimum that allows you to build those according to your own specific needs. It would be very difficult or perhaps even impossible to come up with an implementation that suits everyone's specific cases. Perhaps we could do it by giving the users the option to pick, but then the language would be really huge, it would quickly become bloated. Think about how many ways one could implement a hash table! I like and use ohash extensively though, along with stuff from sys/queue.h. For hash tables, uthash seems to be popular, too.
> If not in the language directly, then ok in the library. But speaking of the library, why limit to just a few things, why not batteries fully included. shoot for knuth in the standard library!
There are many libraries out there. GLib comes to mind. There is also Gnulib, Klib, and God knows what else. I am sure there are a lot of libraries. I have written my own private library of common data structures, subroutines, and so on. You may want to take a look at: https://github.com/kozross/awesome-c
C has an anemic standard library but many libraries you can use as your standard, this keeps everyone happy. I'd expect the javascript/npm world to evolve this way in future.
C is a bit like a Formula One car. Regardless of the historic reasons that caused it to evolve the way it did, it ended up occupying a niche and being very well suited to it, while at the same time being ill suited for other, more general purpose, uses. Under that perspective, change is slow because the people most invested in it want to make improvement happen in a very narrow and precise direction. McLaren, after all, is not going to bat an eye if you complain about its latest model lacking a baby seat!!!
C++, on the other hand, started sort of like NASCAR racing. It wanted to make a racing car out of a mundane, everyday car, and it got very, very good at it. Unfortunatelly, because C++ is based in an everyday car, and because it is Designed-by-Committee (TM), it shows lots and lots of "improvements" that individually kind of make sense, but in the bulk lack any coherence. Nowadays, C++ may perform like the Batmobile (from "Batman Begins" movie) on a good day; but you never know when it is going to bite you in the ass and turn into the Homermobile from the 90's Simpson's TV show.
It is a mix of necessity and lack of insight that many of the people in my generation had to learn how to drive in fucking racing carts!!! But when it's all said and done it gives you a little perspective on how things work and helps you appreciate the differences.
What is the type of the length?
What is the type of the length+string?
What lengths is it limited to?
Do we need a 32 bit and 64 bit version? And 128+ bit version?
Can you provide an implementation? What is its best and worst case efficiency?
What is its expected efficiency for the typical string operations?
I think that the null-terminated representation of the raw string data is a fantastic design; the best of all conceivable alternatives.
char *p = memccpy (d, s1, '\0', dsize);
dsize -= (p - d - 1);
memccpy (p - 1, s2, '\0', dsize);
Notice that you have to recalculate dsize (correctly without one-off errors!), it assumes you have dsize itself, and this doesn't detect overruns (which in many cases you should do).So real-world code would look more like this:
// dsize is the space *available* in d, including \0
size_t dsize = sizeof(d); // if d is an array
// ...
char *p = memccpy (d, s1, '\0', dsize);
dsize -= (p - d - 1);
if (dsize <= 0) goto overflow; // handle overflow
char *q = memccpy (p - 1, s2, '\0', dsize);
dsize -= (q - (p - 1) - 1);
if (dsize <= 0) goto overflow; // handle overflow
It's a little easier to understand than strncat/strncpy versions, it doesn't unnecessarily read its inputs past where they are needed like strlcat/strlcpy do, and it's more efficient than snprintf. So yes, it's an improvement and I support it. However, this is still rather complex; in particular, it's way harder to understand compared to code that uses snprintf, and certainly harder to understand than pretty much any other programming language higher level than assembly.So let's accept this improvement, and keep striving to do better.
while(*p++ = *q++);
which is what sold a generation on the whole idiom. Of course its time is long gone but useful to learn. while(*p++ = *q++);
is simple. But I agree with you, its time has long gone, because in many programs, this code is also wrong. This idiom assumes that that the source can never be longer than the destination. There are now a legion of attackers who will exploit this code and harm its users.Modern C programs often have to work in the presence of attackers.
fp = fmemopen(buf, sizeof(buf), "w");
fputs("one", fp);
fputs("two", fp);
fputs("three", fp);
fclose(fp);
Not sure how it performs, but it reads pretty well IMHO.Look at C++'s std::string in GCC, Clang, and MSVC's standard libraries and respectively its development history. Of course you can make a minimalistic standard string and also eliminate nullpointer checks, trailing \0 checks (everyone passes size_t len anyways), and allocation issues in runtime.
The only standard thing about C strings are vulnerabilities.
That said, if you actually need efficient string operations, you probably want a Rope data structure rather than any libc primitive.
In reality, the alternative to strlcpy/cat isn't "force programmers to write correct code," it's "programmers will just use the crappier available functions with even worse behavior on overrun."
I have seen much, much better designs, that take into account that these functions are rarely called in isolation. In those, calls cooperate with previous and subsequent calls to share the burdens of maintaining correctness and safety.
But that's beside the point. Every major OS's libc has an implementation of strlcpy/strlcat, and OpenBSD's can be readily lifted into a project's source tree as it is portable, a simple code search will reveal the breadth of adoption. The /only/ exception is glibc now. And glibc is not a standards committee, for years the primary objections came from one person.
You're being dishonest.
I also keep coming back to hiding implementation details behind closures. I'm likely not smart enough to understand why that's a bad idea tho.
Simple to implement and use (and also backwards compatible), provided you have a library of common functions for allocating, copying, etc.
If I'm size constrained, I'll consider uint16_t for the length field. If I'm REALLY size constrained, I'll use a VLQ [1] for the length field and take the slight performance hit.
[1] https://github.com/kstenerud/vlq/blob/master/vlq-specificati...
When I do apps I have my own length-prefixed variant.
This ignores how often they are (re)implemented in userland. glib, X and even the linux kernel have implementations. Perhaps we could just standardize what programmers chose rather than allow glibc an unjustified veto?
But memccpy has its own problems. In particular, when concatenating you have to constantly recalculate the "space remaining"; that is just asking for an off-by-one error that leads to a buffer overflow, and makes it more complicated to use. The discussion here doesn't detect attempted overflows, and that's a mistake; you often need to not just prevent an overflow, but you also need to detect an attempted overflow and do something different. You also have to pass \0, which makes the function call more complex (and perhaps under-optimized) since \0 would in nearly all cases be the parameter passed.
So I'm glad this is being added, but it's at most a small step to improving simple string copying and concatenation in C.
[1] https://docs.microsoft.com/en-us/windows/win32/menurc/strsaf...
The C standard tried to add such functions in "Annex K", but unfortunately annex K hasn't received much of a pickup (for various reasons).
So in many places the problem continues.
The way I copy and concatenate strings typically looks like:
int len1 = strlen(str1);
int len2 = strlen(str2);
char *buf = malloc(len1 + len2 + 1);
if (buf) {
memcpy(buf, str1, len1);
memcpy(buf + len1, str2, len2);
buf[len1 + len2] = 0;
}
Of course, not memcpy()ing data around is even better if I can avoid it.As for integer overflow, I don't actually know how to handle it properly. In normal conditions, it is unlikely to be a problem. If the two strings can fit in memory, the sum of their size should fit in a size_t, but I agree that making such assumptions can be a bad idea.
Maybe the best way is to limit the size of the input strings to a reasonable value. That would prevent many out of memory situations too and potential DoS too.
Something like:
if (str1 && str2) {
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
size_t buf_len = len1 + len2 + 1;
if (len1 < buf_len && len2 < buf_len) {
char *buf = malloc(buf_len);
if (buf) {
memcpy(buf, str1, len1);
memcpy(buf + len1, str2, buf_len - len1 - 1);
buf[buf_len - 1] = '\0';
}
}
}
(I probably made a mistake above.)As you suggest, you'll probably run out of memory before you'll overflow, so in reality, you want to check len1 and len2 are some sane value, but of course, library functions don't usually have that luxury. Take a hint from git:
#define unsigned_add_overflows(a, b) \
((b) > maximum_unsigned_value_of_type(a) - (a))
if (unsigned_add_overflows(extra, 1) ||
unsigned_add_overflows(sb->len, extra + 1))
die("you want to use way too much memory");
https://github.com/git/git/blob/6d5b26420848ec3bc7eae46a7ffa...https://github.com/git/git/blob/9d418600f4d10dcbbfb0b5fdbc71...
> making such assumptions can be a bad idea
It's always a bad idea, especially in an unsafe language. Never trust user input.
This does not seem to be the case for me AT ALL. strcpy for example, is a lot faster than memccpy. Here are my results:
$ gcc -O0 bench.c && ./a.out
memccpy: 0.008405
strcpy: 0.002913
$ gcc -O3 bench.c && ./a.out
memccpy: 0.007933
strcpy: 0.002590
$ clang -O0 bench.c && ./a.out
memccpy: 0.008771
strcpy: 0.003225
$ clang -O3 bench.c && ./a.out
memccpy: 0.007966
strcpy: 0.000383
$ musl-gcc -O0 -static bench.c && ./a.out
memccpy: 0.007849
strcpy: 0.005647
$ musl-gcc -O3 -static bench.c && ./a.out
memccpy: 0.005754
strcpy: 0.005625
$ tcc bench.c && ./a.out
memccpy: 0.014252
strcpy: 0.004045
Source code can be found here: https://slexy.org/view/s2EHngPvDh---
The differences seem to be quite interesting. Did I mess up the code? Compare gcc -O3's strcpy and clang -O3's strcpy: 0.002590 vs 0.000383! musl-gcc on the other hand has much more similar results.
---
$ gcc --version
gcc (GCC) 9.1.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ clang --version
clang version 8.0.1 (tags/RELEASE_801/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ tcc -v
tcc version 0.9.27 (x86_64 Linux)You certainly did; both your benchmark functions overflow their internal buffers.
However, it's not all that improbable for memccpy to be slower than strcpy in this case, even if it was used correctly; after all, it does more, and by doing so would prevent the buffer overflow if supplied with correct arguments (specifically, the last one). As to how much slower, I cannot tell. Also, you're skipping over half the point of the article by using a (fixed) source string, the size of which is known in advance.
In general, I would not benchmark library functions on local variables (buffers) without observing their results. It's far too easy for the compiler to remove the call altogether when said removal doesn't make any difference on the output.
You are right. I should have focused more on that instead, that seems more relevant to why the author of the article is suggesting memccpy. I am curious as to whether or not it really is the case that memccpy is "optimally efficient" in practice over the alternatives. Would you like to prove or disprove that statement yourself? I modified the code a bit; it uses strlen to calculate the length of the string passed, and the string is argv[1]. memccpy is still just as slow. Is this a more acceptable approach to you? In this case we do not know neither the string, nor its length in advance. strcpy still outperforms memccpy. Is this sufficient to disprove the claim that memccpy is "optimally" efficient to other alternatives? The other criterion was being widely adopted, in which case, well, strcpy also looks good. Moreover, as dwheeler pointed out, memccpy is a tad difficult to use in practice. I will give strlcpy a try, too, since I prefer that over strcpy. In any case, I am not convinced that these criteria hold true for memccpy over the alternatives.
> on local variables (buffers) without observing their results
What do you mean exactly? I did observe the results of the buffer. See the printf, or are you not referring to that?
> both your benchmark functions overflow their internal buffers.
Would you please elaborate on it, and its relevance? Are you referring to N being too high?
No, the stack size is implementation-defined anyway. Instead, you have a classic off-by-one error because you didn't reserve any space for the final null terminator.
Correctly used memccpy would protect against an issue like this, although the destination string would not be correctly terminated, as it's not a safe string function. Also, if your memccpy version had the correct arguments inside the loop, you wouldn't have needed the extra call before the loop to hide the issue, as the memccpy call would have been functionally identical to strcpy except for the last pass of the loop.
>What do you mean exactly? I did observe the results of the buffer. See the printf, or are you not referring to that?
At least in the link you provided, all the printf's that would observe the contents of buf after the loop are commented out. No observable change happens in the execution of the program even if your compiler decides to just remove any calls to strcpy or memccpy.
--
That being said, strcpy is quite efficient at what you're benchmarking; that is, "multiplying" short strings. The task doesn't highlight its shortcomings. (strcpy wouldn't be too bad even if the strings were longer, although memcpy might be slightly faster.)
But consider the following silly example (not checked for errors) that does highlight the issue:
char *next_insert;
size_t remaining_size;
void append_memccpy(const char *str)
{
char *tmp = memccpy(next_insert, str, '\0', remaining_size); // single pass over str
if (tmp) {
--tmp; // move pointer to terminator from one past it
remaining_size -= tmp - next_insert;
next_insert = tmp;
} else { // insufficient size remaining
str += remaining_size; // first remaining_size bytes are already copied
allocate_more();
append_memccpy(str);
}
}
void append_strcpy(const char *str)
{
size_t len = strlen(str); // first pass over str
if (len + 1 < remaining_size) {
strcpy(next_insert, str); // second pass over str
remaining_size -= len;
next_insert += len;
} else {
allocate_more();
append_strcpy(str);
}
}
Now, even though the latter version is extra silly (just to resemble the former more), it doesn't change the fact that with strcpy, we have to process each byte in str twice. If str is long enough, that might not be exactly free.In any case, could we sum it up? In what cases should memccpy be used over, say, str{n,l}cpy, or even memcpy, and is it in conflict with the article's recommendation or its statement on performance regarding memccpy vs. the alternatives?
First use -march=native on a new clang (>5), and see if clang can optimize memccpy by itself. gcc probably not.
And BTW, gcc-9 is still broken and should be blacklisted everywhere.
> gcc-9 is still broken and should be blacklisted everywhere.
Yes, it seems to be the case. I ran into a few peculiarities, to say the least.
Is that the case? Reading the updated standard draft[0] they also included strdup and strndup. May be they rejected first, then chose to add later.
[0] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2385.pdf
We have the empty string: "\0"
We have the null string: NULL
There is no concept of an INVALID string, as float has NAN.
This would be the result of trying to copy a string to a buffer that is too small.
Or sprintf() into a small buffer.
Or a raw string parsed as UTF-8 and is invalid.
Correctness over efficiency.
You've mentioned NaN propagation in another comment and I think that's a perfect example of the problem with this approach. Sorting a vector of arbitrary floats is a notoriously thorny problem because any float could be NaN, and as NaN is incomparable to any other float, there is no total ordering of floats. There is no general solution to this problem that doesn't involve making assumptions that could be faulty for some applications.
There are sensible answers. But they are weird.
I would say let an INVALID string be length 0. Then accept that catting a valid and invalid string would result in a shorter length.
Which one do you think is safer?
Negative lengths are not compatible with unsigned representation.
A system implementing invalid string values must choose a text encoding such as UTF-8 that supports the concept of an invalid character. Null termination is too flexible. As such is simple length prepending.
I understand how one could shoot down implementations, but none has made a convincing argument about shooting down the idea.
memcpy and company are strictly for raw unencoded buffers.
C doesn’t have the notion of “size of buffer” (yes, arrays have a size that can be queried by sizeof, but only at compile time). You would have to fix that, first.
Isn’t that just NULL?
Let's take strstr, which finds a matching substring needle in a haystack string.
-returns a NULL string if the needle is not in the haystack. -returns pointer to first matching substring.
Extend strstr with VALIDITY
Understood behaviour if both are valid.
Say the haystack is INVALID...as the return value is NULL or a strict substring of haystack, should return INVALID. A poison haystack should poison dependent strings.
Say the haystack is valid but the needle is INVALID...should return NULL. A valid string never contains an INVALID string as a subsequence.
strstr(NULL, /* valid string */)
I can't find the needle in the haystack (actually, I can't find anything in the haystack. I can't find the haystack.) Thus I return NULL. strstr(/* valid string */, NULL)
I can't find the needle in the haystack (actually, I wouldn't be able to find it: I don't know what I'm looking for.) Return NULL.Admittedly, people seem to write string and logging libraries even in languages that do provide them.
* 2 or 4 octets size
1. https://developer.gnome.org/glib/stable/glib-Strings.html#GS...
One of the answers states that GCC and Clang do have support for Pascal strings.
Probably these strings do not work as (well) in #defines, i.e. they don't concat like regular literals.
https://github.com/pjsip/pjproject https://www.pjsip.org/pjlib/docs/html/structpj__str__t.htm
1. Uncheck
article .entry-content {
color: #646464;
}
And a new CSS color rules appears.2. Uncheck
body {
color: #333;
}
Done!I used to think it's ridiculous to manipulate a webpage like this manually, but now I believe: if it's helpful for your for an one-time browsing on a broken webpage, why not?