24 ms·
strcpy: A niche function you don't need
- waynesonfire 5y agoThis shit happens because people think they're clever and can walk a string in a for loop. Its not the fault of c or strcpy just as it's not the gun on trial for taking human life. You're a bad programmer and its too painful to admit. Do everyone a favor, use training wheels and stop coding in C.
- opheliate 5y agoAre you doing okay? Seems like an unnecessarily acidic response to this post, which definitely seems like it's intended for C beginners.
- waynesonfire 5y agoLol ops. If you're a beginner, tell your lead to switch to rust. Strncpy wont help you.
- b33j0r 5y ago(not parent, guessing he was being snarky) While you make a good point, I don’t generally find beginner content on hacker news. I personally evaluated this trending post as something worth my consideration and analysis. Perhaps my news PID needs a tune-up!
- barosl 5y agoWhen I used C for a serious project, I always used `snprintf(dst, sizeof(dst), "%s", src);` to copy a string. It might be a little bit slow, but it freed me from all the headaches of identifying different string functions of C and remembering their subtle differences. It also is useful for other purposes, e.g. prefixing a string.
- AlexanderDhoore 5y agoI do this as well. At some point I googled around and didn't get a straight answer. So I've been using snprintf() for all my string manipulation ever since. My productivity is more important than a sliver of performance.
- mlindner 5y agoDon't use sizeof. If you pass in a pointer you're going to get the size of the pointer, not the length of the string buffer.
- tedunangst 5y agoBut no suggestion for an alternative?
- mlindner 5y agoThere's a lot of alternatives, none of them I particularly like best for all cases. So I won't suggest something I don't think is very good. sizeof is particularly bad however for any kind of strings.
- savannape 5y agosizeof would give you the actual size if you pass in a statically declared array.
- einpoklum 5y agoOne-liner summary: Author suggests using just memcpy() typically, strncpy() rarely/maybe, and even more rarely, or never, strcpy().
- ajanuary 5y agoI don’t think that’s an accurate summary. “Use memcpy() instead. Use strncpy() in limited circumstances.”
- icedchai 5y agoDoesn't memcpy have the same issue as strncpy? the destination will not be null terminated if the source is too long. Many projects just implement a safe_strncpy wrapper that always terminates the destination. Example: https://github.com/brgl/busybox/blob/master/libbb/safe_strncpy.c https://github.com/brgl/busybox/blob/master/libbb/safe_strnc...
- typical182 5y agoI think though it is recommending against strncpy as well… linters and code reviewers commonly recommend alternatives such as strncpy (difficult to use correctly; mismatched semantics) […] Besides their individual shortcomings, these answers are incorrect. strcpy and friends are, at best, incredibly niche, and the correct replacement is memcpy.
- tialaramex 5y agostrncpy() does a specific thing really well. If it's the 1970s where you are and you're writing Unix filesystem code or some 1970s data processing application your program likely has a use for strncpy() and it matches the semantics you need exactly. But it isn't intended as a solution for buffer overflow bugs in your program, and so if you try to abuse it to solve that problem you likely introduce more problems. Imagine your car's air conditioning doesn't work properly. On sunny days it's really much too hot in the car. So, you buy a sunroof. Says "Sun" right in the name, surely that will help right? No. That's not what a sunroof is for. The sunroof works fine as a sunroof but that is not what you needed.
- csnover 5y agoRelated: Designing a Better strcpy, from last month[0] [0] https://news.ycombinator.com/item?id=27537900 https://news.ycombinator.com/item?id=27537900
- corndoge 5y agoNot sure I agree with the recommendation against strlcpy. While it is technically true that if you can't replace strcpy with memcpy you're using strcpy wrong, it's also true that most uses of strcpy are wrong, which I think is a better point to make. The stated purpose of strcpy is to copy a string, and if you're copying a string your best bet is strlcpy. The article is worded in such a way that you'd walk away thinking "I should always use memcpy."
- tptacek 5y agoI'm not a strlcpy fan, but I'll never understand recommending against string functions because they're "nonstandard". They're tiny and portable almost by definition. Vendor them in to your project.
- humanrebar 5y agoIf your project is small and self-contained, sure. If you have a large codebase, you have to at least rename to mylib_strlcpy. Then the other projects do that as well. Then you have 572 almost-clones of strlcpy. Consider this before copying code if you are part of a large codebase. Note that sufficiently popular OSS typically is.
- jonathrg 5y agoIt's too bad that many of the string handling functions in the C standard library are ticking time bombs. I like the approach taken in e.g. git which converts problematic function calls into compile errors https://github.com/git/git/blob/master/banned.h https://github.com/git/git/blob/master/banned.h
- Someone1234 5y agoI like it too, but then you're going down the uncanny valley of: - The project is new, in which case you can easily/safely ban functions, but then why are you starting a new C project in 2021? - The project already exists, and now you need to refactor out all the compile-time errors in order to move forward (time-consuming). Keep in mind the first is a real question that should be answered. If your goal is to avoid undefined behavior/potential security headaches, then C should be entered into after careful consideration of cost/benefits. There are better alternatives for some projects but not others YMMV.
- hdjjhhvvhga 5y agoBoth cases are real. If you start a new project in C, it means you have a very good reason - and hopefully a strategy of dealing with strings and other problematic issues. If you need to deal with an older codebase, the all-or-nothing approach might not be appropriate - incremental improvements might be a better option. Yes, you will have more problems to deal with initially, but with time the situation will get better.
- ncmncm 5y ago> If you start a new project in C, it means you have a very good reason That does not follow. If you do it, you ought to have a good reason, but very probably don't. Any reason you thought you had, if you had any at all, argues for C++ instead, because it works anywhere C does, and enables you to choose to use modern methods that are not inherently error-prone. Whether you do choose to use modern methods, in any non-C language you end up using, is a whole other matter. But at least you can.
- baby 5y agoC: a niche language you don’t need
- legulere 5y agoEven better is to not null terminate strings and use pointer plus length everywhere.
- tialaramex 5y agoRight, when I was younger, I was convinced that NUL termination was a reasonable strategy. Learning C in the 1990s it made plenty of sense, even though I was also learning about buffer overflows and underflows. One of the last things that finally changed my mind was the observation that the length shouldn't live with the text, but with the structure describing the text. Some of you might be laughing now, because that was obvious to you, but I genuinely had gone years without considering that. I'd been imagining a hack like the length of the string lives in a few bytes "before" the text. Once I was envisioning the mutable string as [length, pointer] itself, that seemed obviously better and I was onboard with abolishing NUL termination in software.
- amelius 5y agoIt might sound obvious to you now, but most functional languages conceptually store strings as nil-terminated lists ...
- kaba0 5y agoThe problem is memory safety and functional languages won’t read into another object’s memory even with a logical bug. And a null-terminated linked list is different from a C-string.
- thaumasiotes 5y ago> I'd been imagining a hack like the length of the string lives in a few bytes "before" the text. That's normal, usually called a "Pascal string". As I recall, the C standard makes no assumption of whether strings are null-terminated or not.
- moefh 5y ago
- b33j0r 5y agoIf this is the case, why aren’t the stdlib functions defined this way? In all of the history of the longest-lived production language family besides FORTRAN, this blog post is the first voice to point out that memcpy should be the same operation as strcpy for null-terminated strings? What is going on here? (Rust crowd snickers as they unwrap<‘jk> &mut *foo_buf)
- gompertz 5y agoCurious as well, but when I look at the glibc source for strncpy it is calling memcpy....https://code.woboq.org/userspace/glibc/string/strncpy.c.html https://code.woboq.org/userspace/glibc/string/strncpy.c.html It all seems to depend on the compiler vendor.
- comex 5y agoYou could implement strcpy(a, b) as memcpy(a, b, strlen(b) + 1), but it would be slower, since it makes two separate passes over b (one to calculate the size, one to copy). The post seems to be arguing that in most cases where you would call strcpy, you should know the size already, because otherwise you wouldn't know whether the source string was short enough to fit in the destination buffer.
- jabl 5y agoC2X will be adding memccpy() (note two c's in the middle, not memcpy!). Overview and justification at https://developers.redhat.com/blog/2019/08/12/efficient-string-copying-and-concatenation-in-c https://developers.redhat.com/blog/2019/08/12/efficient-stri...
- AlexanderDhoore 5y agoGreat naming choice. Won't be confusing at all.
- 1500100900 5y agoIt's been available in FreeBSD since mid 90s and yet nobody ever uses it, not even FreeBSD developers. I like memccpy because it's easy to check for all possible errors known to me, but everybody else seems to prefer strlcpy because it came from THE security-oriented BSD.
- andrewmcwatters 5y agoWhat's the standard practice these days in C to move strings with lengths around? I've been out of C for at least a couple of years now, but I can't imagine it's changed in that time.
- dragontamer 5y agoI'd assume memcpy. But a more serious answer is to use C++ strings instead, lol. Writing "C-like C++" is probably more beneficial. I do realize that a lot of people prefer to write in pure C (ex: Linux kernel team), but more and more people are realizing the benefits of C-like C++ code.
- andrewmcwatters 5y agoYeah, I would assume as much, too. When I last worked in C, there was no de facto way to do this since so many functions just worked on null termination.
- jstimpfle 5y agoIf you're asking what to do about copying strings, then it's either memcpy(), or rarely str[n]cpy(). strcpy when I can assume the source length is safe but don't know the size of the underlying buffer. strncpy when I want to check the return value and maybe issue an error. For passing references around, I use whatever works. Plain `const char *` argument is certainly a frequent choice for simple name or filepath arguments. That can even mean doing the occasional strlen() when making a copy of that string. It doesn't bother me at all; overall zero-terminated strings are very easy to use. Can't understand why people never stop bitching about it. When the string is not just an opaque ID, but needs to be examined more closely, it's usually more of a "slicey" or a buffer-processing problem - then I'll add an `int len` to the list of arguments, or to the members in a struct. Very rarely I'll create a String class, but usually I don't bother. It feels to me like going against the grain of the language. I don't want to create my own host of string processing functions that take this String as argument, when it's usually simpler to operate directly on the data. Something that I close to never need is the "growable" string class with memory management like std::string. I have no idea right now why I would need such a thing. I tend to write my programs to work on fixed buffers. At most I'll create dynamically sized strings, but a generic string that can grow after creation isn't a frequent use case.
- forrestthewoods 5y agoGod the C runtime library is so bad. So is the C++ STL. I think it’s a travesty that these languages defined an API but didn’t provide an implementation. Hindsight is 20/20, but what a nightmare! It is far more rational to provide an implementation using standard language features. It’s not like strcpy needs to make a syscall!
- nly 5y agoNot sure why you brought the STL in to it. Copying a std::string is as easy as assigning the variable, or calling the assign function with a char* and length
- jstimpfle 5y agoHowever, that will require a 1000s of SLOC implementation of a weird class type with lots of subtle semantics, and a generally inefficient behaviour (small heap allocations).
- forrestthewoods 5y agoI’m asserting that the CRT API design is bad. And following up that C++’s isn’t any better. std::string does better facilitate copying strings. But string manipulation with std::string is really really bad. I’m also saying that the concept of defining an API but not providing an implementation is insane. That is, imho, extremely inappropriate at the language level. Differences in behavior between C and C++ STL implementations on different platforms is infamous. And almost entirely unnecessary.
- TazeTSchnitzel 5y agoMy favourite C string function is snprintf: • It takes a buffer size and truncates the output to the buffer size if it's too large. • The buffer size includes the null terminator, so the simplest pattern of snprintf(buf, sizeof(buf), …) is correct. • It always null-terminates the output for you, even if truncated. • By providing NULL as the buffer argument, it will tell you the buffer size you need if you want to dynamically allocate. And of course, it can safely copy strings: snprintf(dst_buf, sizeof(dst_buf), "%s", src_str); Including non-null-terminated ones: snprintf(dst_buf, sizeof(dst_buf), "%.*s", (int)src_str_len, src_str_data); And it's standard and portable, unlike e.g. strlcpy. It's one of the best C99 additions.
- jstimpfle 5y agosnprintf is underlying most logging modules I've done (logging to memory / file / network / console...) - I've been thinking about doing custom formatting routines but there's surprisingly little need for them. You probably know this, but sizeof is not a function. I prefer the easier to type snprintf(buf, sizeof buf, ...);
- chrsig 5y agoFor anyone curious, this is something that's been discussed on HN at length as a result of a lkml thread https://lkml.org/lkml/2012/7/11/103 https://lkml.org/lkml/2012/7/11/103 https://news.ycombinator.com/item?id=9629461 https://news.ycombinator.com/item?id=9629461
- wahern 5y agoLinus disproves his point. First he claims that sizeof behaves like a function, then in the next breath, realizing the flaw in his logic, proceeds to describe and excuse the counterpoint: sizeof(*p)->member. This is classic Linus--too emotionally invested in a preference. Except in this case it's particularly pointless and unjustified. sizeof is an operator. Period. The point of not using parentheses is to continually drive that point home. It's praxis. Of course, it's not unreasonable to prefer using parentheses. And there's a middle ground: most C styles nestle function identifiers and opening parentheses in a function invocation, whereas they require a space between operators and binary operands. So if you prefer using parentheses, whether all the time or just in a particular circumstance, you can do: sizeof (*p)->member or sizeof ((*p)->member) That's not entirely consistent. sizeof is a unary operator, and style guides tend to prefer nestling unary operators while spacing binary operators. But nobody is trying to be pedantic here. The issue is readability, minimizing typos, and dealing with the fact that the sizeof operator, while defined as and behaving exactly like a unary operator, doesn't look like one. Also, it's worth pointing out that not only does the C standard itself literally define sizeof as a unary operator, all the code examples in the standard put a space between sizeof and its operand. It's a stylistic convention, but hardly arbitrary. By contrast, there are other constructs, like _Generic, where the code examples do NOT use spacing. _Generic is a specialized construct altogether, but syntactically it behaves somewhat like a macro, and it's customary to style macro invocations like function invocations.
- Animats 5y agoShould have been deprecated around 1990 and removed by 2005.
- ncmncm 5y agoKnow what's even worse than strcpy? strlcpy. Every use of it I have seen was subtly incorrect. To use it correctly takes more code than anybody wants to write, or (AFAICT) ever does. strcat is worse than both, though.
- aidenn0 5y agoMSVC is a very curious choice of strcpy_s implementation given that MSVC is openly not compliant with C11, much less Annex K. OpenWatcom, which implements a late draft of the standard behaves as expected on their testcase: Runtime-constraint violation: strcpy_s, s1max > RSIZE_MAX. ABNORMAL TERMINATION The reason not to use the _s versions isn't that they are bad, it's that basically nobody has implemented them (hence me having to use Open Watcom to demonstrate this example) [edit] Just noticed that in the page they linked to at the top when they mention strcpy_s, it notes that the MSVC implementation predates even the original draft of the standard and lacks RSIZE_MAX.
- pjmlp 5y agoMSVC is compliant with C11 and C17, minus optional annexes, they are after all optional and not required for ISO compliance certification.
- aidenn0 5y agoThanks for the correction. C support in MSVC was terrible just a few years ago, but googling I see they added better C support starting in 2019
- matheusmoreira 5y agoNot just strcpy either. Pretty much all str* functions are bad and should be replaced with their mem* equivalents when available. I don't even know why the str* functions exist. They're just worse versions of mem* functions.
- jstimpfle 5y agoThey exist to support working with zero-terminated strings, which worked just fine today, and IMHO are useful for identifiers even today (easy to use + some modest memory savings). Yeah, some stuff like strcat, strtok, or maybe even strcpy is taking it a little bit too far and is too inviting of errors. 30-50 years ago there was often a practice of not minding security aspects of processing external data. But even today, you could use those safely if you know the data. strtok especially is one arcane function that was probably used a whole lot more back then. Today, hardly anybody does fixed-character delimited fields. strtok was probably useful to "parse" /etc/fstab and formats like that with as little own code as possible :-) Some functions I use from time to time: strcmp(), strncmp(), strcpy(), strncpy(), strstr(), strchr(), strrchr(). One can rewrite versions of them to work on different types of strings, but they're readily available (if you allow libc dependency). There might be a speed advantage to home-grown solutions, too - although that's not really the point, and if performance matters the bottleneck shouldn't be on such pedestrian string processing anyway.
- howtofly 5y agoI found the article kind of misleading: using memcpy for c-string is generally a bad idea, unless string length is bound with string buffer like std::string. Otherwise it will make code review very difficult. In our team c-string is prefixed with sz_, e.g. char sz_name[13], and we always use a safe subset(or a safe replacement) of strxxx functions with these sz_ prefixed variables. Using memxxx with sz_ variables is explicitly forbidden, since it may break the NULL-terminating contract. The sz_ prefix convention is by no ways like the hungarian naming nonsense. Suppose that you have "char sz_name[13]" in a structure of configuration parameters, sz_ tells the guy changing the field to keep it NULL-terminated, if they don't, it's their fault. On the other side, users of this field can safely use printf("%s", sz_name) without the risk of crashing the program. For safe replacements of strcpy, I recommend: https://news.ycombinator.com/item?id=27537900 https://news.ycombinator.com/item?id=27537900