As an example of the alternate approach, the Rust language uses (ptr, size) to represent all arrays (called slices) and strings internally. This seems like a much better method, allowing you to reference substrings without allocating a buffer, and perform out-of-bounds checking at runtime.
I think unicode support would be much more important, but not everyone agrees (for example, on embedded devices unicode support can be dead-weight). And adding to C without consensus leads to lousy decisions.
char *heap_ptr, *stack_ptr; ... fn_needs_heap_ptr(strcpy(heap_ptr, stack_ptr));
Another example (dma address space != buf a.s): char *dma_ptr, *buf; ... fn_needs_dma_ptr(strcpy(dma_ptr, buf)) ...
IOW, foo(strcpy(dst, src)) is VERY different than strcpy(dst, src); foo(src);You will indeed have a problem if you strcat() excessively, but then it will be a marginal case, so you will optimize for it in your own code rather than dragging it into the standard.
Cheap? 12 precious clock cycles is enough for up to 384 floating point operations (FMA).
Not sure how fast or slow rep sca/mov is these days. Regardless, strlen that is not scanning at least 4 bytes per clock is hopelessly slow. I think you can do at least about ~16 bytes per clock.
Anyways, the problem with strlen is unnecessary extra (potentially mispredicted) branches and memory accesses.
I have seen enough cases where strlen has dominated cost in the profile. Of course this depends entirely what you are doing, there are plenty of workloads where strlen just doesn't occur.
Though you still pay for the memory/branch prediction stalls.
There will be bottlenecks narrower (is that the right term?) than string operation in any project not dealing exclusively with strings. In the latter case, rolling your own string utilities may make sense
C string operations might have been elegant 40 years ago, but nowadays they're like rerouting a 747 to check if office lights are on.
Strlen is doing a lot of work for little benefit. It does slow and power hungry memory accesses. Because it's scanning for terminating 0x00 byte, it needs to contain a branch -- and loop terminating branch must be a costly mis-predicted one.
C printf and friends are even more insane. It scans "bytecode" instructions from a string and does dynamic formatting. You can do format options like this:
printf("% 0#*.*f\n", 15, 5, 1.234);
Or say print five chars of an unterminated string, left padded to total length of 10: printf("%10.5s\n", unterminated_str);
It won't (at least it shouldn't) crash even if 6th character is on an unreadable memory page.The last example is explicitly defined in the Standard.
Implementing all that subtle functionality correctly takes up a lot of CPU time.
According to my quick test, on Visual Studio 2012, even simplest sprintf with just one parameter seemed to take about 1 microsecond to execute.
Of course clang and gcc seem to sometimes compile whole format parser away. At least...
printf ("Hello World!\n");
... is optimized into a simple "puts("Hello World!");".Of course iostream << operator runtime performance is also pretty horrible. Each << invocation seem to call streambuf::sputn (or sputc) separately. On VS2012, simplest stringstream test...
ss << "value: " << intVal << endl;
... outputting one variable into it and turning the result into a std::string took about 3 microseconds. (Although it was a very quick test, a number of things might be suboptimal in the test code.)