Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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.

Comments on Are memccpy(3) or strncpy(3) bad for copying and catenating strings with truncation?

Parent

Are memccpy(3) or strncpy(3) bad for copying and catenating strings with truncation?

+0
−0

I heard strncpy(3) is bad, and also heard that C23 added memccpy(3) to replace it.

However, I also heard memccpy(3) is even more terrible than strncpy(3).

Are these functions really bad? How so? Are there any legitimate uses of any of these functions? What should we use instead?

History

1 comment thread

strncpy duplicate (1 comment)
Post
+1
−1

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 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 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:

#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:

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:

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.

History

5 comment threads

Usage for copying without truncation (3 comments)
Input sanitation (2 comments)
UB: Pointer arithmetic overflow (1 comment)
fgets(3) (2 comments)
mempcpy(3), minimum viable product (1 comment)
Input sanitation
alx‭ wrote 2 months ago · edited 2 months ago

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.

strtcpy()/strscpy(9) is perfect for sanitizing input. It is in fact the main function used in the Linux kernel for sanitizing user pointers that are to be interpreted as strings --but may be malformed, without a \0--.

stpecpy()/strecpy(2) is also perfect for sanitizing input. It is equivalent to strtcpy(), except it returns an offset pointer, which as you said, is more useful.

I don't think it's apples to oranges.

System‭ wrote 2 months ago

Thread renamed from "Input sanitazion" to "Input sanitation" by alx‭