Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
I won't address strncpy here since that's fully covered by Is strcpy dangerous and what should be used instead? But I can also add that C has no type support for fixed-width strings, so it was the...
#1: Initial revision
I won't address `strncpy` here since that's fully covered by [Is strcpy dangerous and what should be used instead?](https://software.codidact.com/posts/281518)
But I can also add that C has no type support for fixed-width strings, so it was therefore nonsense to add fixed-width string handling functions to the ISO C standard. If fixed-width strings were to be covered, then the appropriate data type for that should have been added to the language too, but that wasn't done so it is all misguided all the way back to C89. As noted in the link, the spirit of C was never to store the array size together with the data, for good and bad.
---
Regarding `memccpy`, I would agree that the paper [N2349](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2349.htm) is a bit confusing in several ways, speaking explicitly about strings etc. But that's irrelevant since the actual ISO/IEC 9899:2024 document doesn't say anywhere that the purpose of `memccpy` is to truncate strings. Other than it residing inside `string.h`, which is also the case for `memcpy` and `memmove`, so putting it in another header would be confusing.
What the C standard does say (7.26.1):
> For all functions in this subclause, each character shall be interpreted as if it had the type `unsigned
char` (and therefore every possible object representation is valid and has a different value).
That's nice because then we can rule out misalignment bugs and trap representations. But also signedness of `char` mishaps as can happen in the broken `ctype.h` library when an implementation decides to _not_ treat the passed parameter as `unsigned char`. So it's already safer than a lot of the standard lib and we do get a pointer to where it stopped copying, so we can calculate the size copied. Which really ought to be "minimum viable product" for any function that writes/copies, yet that is impossible in other standard functions that come with a broken API, for example `fgets`.
The `mem...` prefix promises that this is a bare bones function which you can't expect to have a ton of safety built-in, because it needs performance, likely to be inlined/replaced by the optimizing compiler. And so `memccpy` has the same well-known limitations as `memcpy`: you can't use it for overlapping memory and there is no type safety what-so-ever, since the function is supposed to be used on raw data.
A natural use for the function could for example be to copy stuff from raw memory cells in an embedded system NVM - there may be some use-cases for such in flash memory wear-leveling algorithms for example, or protocol handlers with a fixed sync word in the end. By using a library function rather than hand-crafting it out yourself, there's a bigger potential for compiler optimizations.
Now of course it _can_ also be used for string copying, with the head's up that the function stops _after_ copying the first occurrence of the searched-for character, so it copies the null terminator too, if found. Otherwise it returns a null pointer.
That's the only valid criticism I can come up with for this function - perhaps you wouldn't expect a function called `mem...` to add null termination etc but rather stop copying _before_ finding a particular character (a sentinel value). So that may be a bit surprising if not reading the function's documentation too carefully. Consider `memccpy(buf, from_stdin, '\n', size)` - hey why did you copy that crappy line feed for, I don't want it!
If you do use it for strings, I find the usage rather straight-forward:
```c
#include <string.h>
#include <stdio.h>
int main(void)
{
char s1[] = "hello world";
char s2[100];
char* result = memccpy(s2, s1, '\0', sizeof(s2));
if(result == NULL)
{
/* error handling here */
}
puts(s2); // already null terminated, we're ready to go
}
```
The null terminator ends up in the right place no matter if the function stopped before the end of the buffer or not, because it is copied by the function. If we want to know if the whole source string was copied or not, we can do this:
```c
const char* expected_end = s2 + sizeof(s1);
char* result = memccpy(s2, s1, '\0', sizeof(s2));
...
if(result == expected_end)
{ ... }
```
At every place in the code we use the size of the buffers, never the length of some string, so there isn't really any chance of off-by-one errors because of that.
C does explicitly allow pointers to point one item beyond an array for exactly these kind of scenarios with "end pointers".
Similarly the size copied is easy to obtain:
```c
printf("Size copied: %tu\n", result-s2);
```
(You do get it as the exotic `ptrdiff_t` though, rather than `size_t`. Casting between the two should be safe however.)
If you want the string length rather than the size, you'll naturally need to subtract by 1 to not count the null terminator.
So to me, this is a pretty good function for copying strings, with a rather straight-forward API.
As for comparing `memccpy` with functions that don't take a buffer size as parameter, that's comparing apples and oranges. If you don't use the buffer size, then you can't use the function for stuff like input sanitation, which would be a possible use-case for `memccpy`. Unlike all of the `str...` functions which as indicated by the prefix are to be used for strings, not for input sanitation.
---
Regarding optimizations:
Yes, `memccpy` won't be the fastest possible on a target relying on branch prediction. That would be `memcpy`. If we are talking micro-optimizations, it is however possible to create two different implementations of `memccpy` inside the standard lib: one for the scenario where the character to look for is zero, and one for any other scenario. That might save a few ticks on an ISA where check vs zero is faster than check vs a value.
_However_, compilers already do optimizations to enable such checks against zero when they are iterating over a known size. So rather than implementing an up-counting loop that checks against the value `size_t n`, it can start iterating on that value and implement a down-counting loop that checks against zero.
