News for hackers. Stuff that matters.
Re:Massacring C Pointers by the_lurker on Tuesday June 26, 2018 @01:43AM (#17398157)
Yesterday I encountered a similar program on a HN comment chain as shown in this link. I am genuinely confused as to why this program is bad. I am a student and I do not know the best practices regarding pointers, but it is how I would write a program to combine two strings.

Can someone please elaborate why it is bad? Are their any good resources to fill gaps in my knowledge?

Thanks in advance.

Edit: Thank you guys for pointing out so many problems. It seems that I have a lot to learn. :)

Re:Massacring C Pointers by int0x80 on Tuesday June 26, 2018 @02:21AM (#17398305)
It is wrong in many ways.

It copies s and then t to a fixed size buffer, without any checks. That will write to invalid memory (probably smashing the stack) if len(s), len(t) or len(s) + len(t) > 100.

It returns a stack allocated buffer (r) pointer to the caller. The array will be invalid when the function returns, as the automatic variables only live in the function scope (during the call), they are deallocated when the function returns.

To do this right you have various strategies.

1. Allocate a buffer of len(s) + len(t) + 1 with malloc, copy the strings and return it. Have the caller free it when it's done. It can be inapropiate because of the dynamic allocation.

2. Have the caller pass a destination buffer and its size. If you know the char *'s are zero terminated, check if you have space for them in the dest buffer. If not, truncate or error out. Most of the times, this is the prefered solution.

3. Use a static local buffer, and return it to the caller. You may need to truncate the copy too. Not recomended. This is not a good solution as the function also will not be reentrant (unsafe with multiple threads).

You can use libc functions like strncpy (C89+), snprintf (C99+) etc to make a "size-checked" copy with various automatic truncation semantics. You can refer to their man pages for details.

Edit: to the downvoters, please point out what is wrong in the comment.

Re:Massacring C Pointers by simias on Tuesday June 26, 2018 @04:36AM (#17398814)
You're right, although any mention of strncpy should come with a big disclaimer that it's effectively broken because you can end up with a non-NULL terminated string in some cases. strlcpy should be the way to go but unfortunately it's not part of the C standard and not available everywhere (sometimes for rather bullshit reason IMO, but that's a different story).
Re:Massacring C Pointers by jmts on Tuesday June 26, 2018 @08:27AM (#17399972)
Documented behaviour is a little different to "effectively broken". The difference between strncpy and strlcpy is that strlcpy will NUL terminate the last byte for you always. There is nothing stopping you from doing the same thing yourself when you use strncpy. If you care enough, write your own strlcpy - it's only one extra line.
Re:Massacring C Pointers by simias on Tuesday June 26, 2018 @08:37AM (#17400045)
I'm not saying it's hard to work around but I maintain it's broken. You have a function that deals with C-string that in some conditions returns something that's not a C-string but can't be trivially distinguished from one and will trigger undefined behavior if used like one. It's terrible ergonomics and almost certainly not what you want to do in any situation.

You can argue that truncation is an error condition but then it ought to notify it somehow, for instance by returning NULL in such a case. And even then it's incoherent with snprintf which doesn't have the same behaviour and does always terminate with '\0' even in case of truncation (assuming non-0 buffer length, of course).

It's just an unnecessary footgun that serves no practical purpose. It would be like a date function that gives you the today's date except on the 4th of December where it replies that it's the 31st of February. Not hard to work around but still broken.

Re:Massacring C Pointers by caf on Tuesday June 26, 2018 @09:39AM (#17400459)
It's not broken, but it is misnamed.

This is because it is not intended to work with the same kind of string that the other str* functions work with (ie. an ordinary null terminated string).

Instead it's supposed to work with fixed-width string fields that pad out values shorter than the field width with nulls. This is how original UNIX directory entries were stored.

See how the name is copied into u.u_dbuf here: https://github.com/hephaex/unix-v6/blob/daa355109625a50e6b10...

Re:Massacring C Pointers by simias on Tuesday June 26, 2018 @10:37AM (#17400885)
I see your point but at this point I think it's just a matter of taste. I don't really see how having a function meant to deal with a special case of character buffers disguised as a general purpose string manipulation routine in the stdlib could be considered reasonable. I understand why it's here, I understand the history, I understand why it made sense at some point to have such a function but you won't be able to convince me that it's not broken or that it shouldn't be deprecated in favor of strlcpy (ditto for strncat/strlcat). After all it is in <string.h>, not <fixed-width-string.h>, it's pretty heinous that it fails at the very low bar of actually producing a valid C string every time (especially given the very high prejudice of having rogue unterminated "strings" in a C program).
Re:Massacring C Pointers by caf on Wednesday June 27, 2018 @02:01AM (#17406070)
It seems fairly unlikely that the C standard would add strlcpy() and strlcat() when it already has strcpy_s() and strcat_s() in Annex K.
Re:Massacring C Pointers by heisenbit on Tuesday June 26, 2018 @02:02PM (#17402757)
A misname that may have cost cost billions in bugs and security issues.
Re:Massacring C Pointers by burfog on Tuesday June 26, 2018 @11:22PM (#17405548)
It has also avoided security issues, some of which get created when people get the idea that every strncpy must be replaced by strlcpy.

The strncpy function writes to the entire buffer. This is important if you will be passing the buffer across a security boundary, for example in a network packet or as a struct copied into a publicly visible file. If trailing bytes are not cleared, then secret data (which happened to be sitting in memory) can get leaked.

Re:Massacring C Pointers by dxhdr on Tuesday June 26, 2018 @11:31AM (#17401372)
I agree it's terribly named, however discussing the behavior of strncpy is important because the casual reader or new C programmer will see "don't use strncpy, it's broken" and then come away with the wrong idea. strncpy is not inherently broken but it's most likely not the correct function to use. As a C programmer it's important to understand why and what the alternatives are (and there are several).
Re:Massacring C Pointers by jschwartzi on Tuesday June 26, 2018 @01:22PM (#17402438)
I can use a sledgehammer to break my leg, but that doesn't mean the hammer is broken by design. I just have to be careful where I swing the hammer and what I hit with it.
Re:Massacring C Pointers by simias on Tuesday June 26, 2018 @01:36PM (#17402565)
If the sledgehammer defaults to "leg breaking mode" then it's broken. How often do you actually use strncpy actually intending it not to return a non-NUL terminated string on truncation?

I hate this mentality in a lot of C circles that boils down to "there's no bad language, just bad programmers, man up pussy". I like C, I use it a lot, it's one of the first languages I learned and it's been my main "professional" language for more than a decade. Yet I can also see that it has many unnecessary sore points. Having switch not break by default, gets(), array shenanigans, some aliasing rules, the hundreds of completely different meanings for "static", macro hygiene and I could go on... You can say "it's not a big deal and it's not going to change at that point anyway" and sure, I'm not arguing for a revolution, but let's not act that it's an absolutely perfect language and I'm an idiot that doesn't get it for pointing out these issues.

Re:Massacring C Pointers by wruza on Wednesday June 27, 2018 @09:17AM (#17407871)
Sledgehammer defaults to nothing, it just lies around before someone takes it and makes an action. You're basically saying that if a label on the tool misleads you, then you're not in charge and the tool is broken. That doesn't work in engineering, nor is it forgivable to not meet all formal requirements (like reading the documentation thoroughly). You have to look at your tool to find its best fit or at least to find a handle; moreover, labels mean nothing - they are just nicknames for synopsis. Who thinks otherwise should consider javascript or alikes. We live in a world where labels create the entire meaning, but let's not fall into this idiocy in workplaces.

The linux manual for strncpy makes few good notes about its usage and that has a meaning, since it is official and will not change due someone ranting about something again.

Re:Massacring C Pointers by int0x80 on Tuesday June 26, 2018 @02:24PM (#17402906)
Absolutely. strncpy is a very badly designed interface. Is it documented? Yes. Does that make it any good? No.

Principle of least surprise. Idiot proof. Semantics that match common usage. Call it what you like.

Re:Massacring C Pointers by jschwartzi on Tuesday June 26, 2018 @07:37PM (#17404764)
I'm not arguing that it's a completely perfect language. But broken is an incredibly strong word for something that works when used as described in the documentation.

I think the API designer was trying to be parsimonious and not assume too much about what you want to do with the data in your destination buffer. The requirement was to provide buffer overrun protection when the source string is too large, and this function provides that. Beyond that, the requirement that the character array be null-terminated is your decision.

In fact, if you determine that it isn't null-terminated you can do other things besides null terminate it yourself. You might want to provide a helpful error message to consumers of your API rather than truncating their input data silently. Or you may try reallocating your buffer until the string fits. There's actual error handling that you can do with this function. In contrast, automatically null terminating the string makes that more difficult.

The other issues that you have with C seem like preferences. There's nothing wrong with not breaking by default in a switch statement as long as you know that that's what happens.

Re:Massacring C Pointers by ehaliewicz2 on Tuesday June 26, 2018 @03:07PM (#17403222)
That's a poor analogy. It calls itself a string function but isn't. So it's more like a airsoft gun that actually shoots bullets, but you don't know until you decide to shoot yourself in the leg to see what it feels like.
Re:Massacring C Pointers by wruza on Tuesday June 26, 2018 @11:33AM (#17401391)
If you had to name it according to unix traditions, how would you?
Re:Massacring C Pointers by buckminster on Tuesday June 26, 2018 @01:21PM (#17402430)
Maybe fldcpy and fldcat to make clear than a fld (field) is entirely distinct from a str. A better choice if you have a time machine would be to remove these from standard C altogether.
Re:Massacring C Pointers by pedasmith on Tuesday June 26, 2018 @03:42PM (#17403453)
A documented behavior which causes many security and crash issues is, almost be definition, effectively broken.

Can you come up with an example of an API which you would consider to be effectively broken and yet is not actually broken? Presumably, it would be an API that's easier to misuse than strncpy.

Re:Massacring C Pointers by _kst_ on Tuesday June 26, 2018 @03:02PM (#17403197)
NULL is (a macro that expands to) a null pointer constant. The null character '\0' is commonly called NUL.

I've written about strncpy: http://the-flat-trantor-society.blogspot.com/2012/03/no-strn...

Re:Massacring C Pointers by int0x80 on Tuesday June 26, 2018 @09:36AM (#17400435)
Yes, I dont like the strncpy interface, it is very error prone. I just gave it as a posibility. But I did refer to the man page and said "various truncation semantics" because of that, precisely, without going into more detail though.
Re:Massacring C Pointers by jmts on Tuesday June 26, 2018 @02:41AM (#17398392)
Not sure why you're being downvoted. That's all pretty standard.

Method 1 is a contract you often make as part of defining the interface. For an example, such things are out of scope so this is a perfectly reasonable point.

Method 2 is common. Most of the strn*() and snprintf() etc, do this. This is the preferred method for some of my colleagues.

Method 3 is used in the standard library, though as mentioned is usually avoided.

Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:48AM (#17398418)
I'd suggest not insulting the people reading your comment if you want some points back…
Re:Massacring C Pointers by int0x80 on Tuesday June 26, 2018 @02:58AM (#17398453)
You are right. Fixed.
Re:Massacring C Pointers by krylon on Tuesday June 26, 2018 @01:53PM (#17402674)
> snprintf (C99+)

It's been years since I have written C professionally (and I only did it for two years), but I felt that snprintf was the single biggest improvement that C99 brought to the table, at least for people that had to deal with strings. ;-)

Re:Massacring C Pointers by kevin_thibedeau on Tuesday June 26, 2018 @01:37PM (#17402567)
Friends don't let friends use strncpy(). It doesn't guarantee NUL termination and wastes time zero-padding when it is rarely desired. Use C11 strncpy_s() or platform specific equivalents.
Re:Massacring C Pointers by int0x80 on Tuesday June 26, 2018 @02:26PM (#17402922)
Agree.
Re:Massacring C Pointers by leodeid on Tuesday June 26, 2018 @02:15AM (#17398282)
I haven't touched C in years, but here's my descending "wtf" list:

1. Returns pointer to stack-allocated data, which immediately becomes invalid. Instead, it should be using some sort of allocation (e.g. 'malloc'), or taking in a destination pointer.

2. 'r' is arbitrarily set with length 100. Smaller strings don't need all that space, and larger strings definitely will overrun.

3. The function signature is really awkward. Without any of the surrounding textbook content, I'm not sure what behavior is supposed to happen. At first, I expected something like 'strcat', which takes two char* and appends the second one to the first one. But that isn't happening here and instead it seems to require dynamic allocation. (Hiding allocations inside a function is generally kind of weird. Usually the caller should be responsible for passing in a handle to the destination.)

4. There's no sensical limit on the loop iteration. If the input 't' doesn't have a null terminator, this is going to throw a ton of garbage into the stack space (because 'r' is stack-allocated to a fixed size). And also maybe run for a really long time.

5. 'strcpy' should usually be replaced by 'strncpy', which performs the same function but also requires you to provide a limit ("copy this string, but at most 'n' bytes"). That prevents a class of exploitable errors known as "buffer overruns". I don't know when the 'n' string functions were added to C or became popular, though.

This is a teaching exercise, so the fact that this is implemented as a separate function instead of calling 'strcat' from <string.h> doesn't seem like a big problem.

Re:Massacring C Pointers by bboreham on Tuesday June 26, 2018 @03:07AM (#17398492)
> 'strcpy' should usually be replaced by 'strncpy'

Sorry to butt in, but this is a bit of a trigger for me: I’ve had to fix a number of programs infected with this idea.

The main problems with strncpy are:

When the source string is shorter than n, strncpy will pad the target to n bytes, filling with zeros. This is bad for performance.

When the source string is longer than n, strncpy will copy n bytes but _not_ nul-terminate the target. So you need extra schenanigans every time you use it to cover this case.

So strncpy is hardly ever a good idea. Sadly there is no standard replacement that is widely accepted. More details at https://en.wikipedia.org/wiki/C_string_handling#Replacements

Re:Massacring C Pointers by userbinator on Tuesday June 26, 2018 @07:37AM (#17399664)
I agree with you completely, and in general think the whole idea of using "safe" string functions with built-in buffer length checking is wrong because it is a solution to a symptom, not a cause.

Before writing to the buffer you should've ensured that it's big enough, and decided what to do if it's not, long before actually doing it. In other words, what happens if it's not big enough? These "always use $length_checking_function" proponents miss that point. Yes, you've avoided an overflow here, but chances are something was already too small long before the flow reached here, and the fix is not to replace an overflow with truncate/not copy/etc. here, but fix the check/sizing that came before elsewhere.

Re:Massacring C Pointers by ben509 on Tuesday June 26, 2018 @04:56PM (#17403917)
> Before writing to the buffer you should've ensured that it's big enough, and decided what to do if it's not, long before actually doing it. In other words, what happens if it's not big enough?

If you planned all this out, you're still making an assertion as to the length. The contract is "give me a string of this length" and if that's not enforced by the compiler, it ought to be enforced at runtime so that the error is detected and dealt with as soon as possible.

So maybe "safe string functions" should really be "fail fast string functions."

Re:Massacring C Pointers by grive on Tuesday June 26, 2018 @04:30AM (#17398793)
> Sadly there is no standard replacement that is widely accepted.

True, but practically: when having access to BSD extensions, strl* are used. If only C standard is available, snprintf is preferred. I have seen C libs that will check for strl* availability, and if not, reimplement them using snprintf.

So for portability, snprintf is the way to go. For correctness, and pushing for their extended use, strl* is nice.

Re:Massacring C Pointers by dbt00 on Tuesday June 26, 2018 @03:30AM (#17398574)
The least bad options seem to be strl* or snprintf(..., “%s”, ...) but yeah nothing is perfect.
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @03:13AM (#17398520)
There's strlcpy, but it's not part of POSIX unfortunately.
Re:Massacring C Pointers by slrz on Tuesday June 26, 2018 @04:53PM (#17403899)

  #define strlcpy(d, s, n) snprintf(d, n, "%s", s)
Not quite the same (different return type) but close.
Re:Massacring C Pointers by minipci1321 on Tuesday June 26, 2018 @02:47AM (#17398414)
> 5. 'strcpy' should usually be replaced by 'strncpy'... That prevents a class of exploitable errors known as "buffer overruns".

To be honest, strncpy is barely better in this respect (as a security improvement) - truncating against arbitrary size limit in this day and age of text-only protocols... I wonder if outright crashing at the testing stage would be preferable rather than subtle misbehavior creeping into the release.

Both are bad IMO, the actual required buffer size should be known in advance.

Re:Massacring C Pointers by blt on Tuesday June 26, 2018 @01:27PM (#17402484)
Raw null terminated strings are just a bad idea. `std::string` and the bafflingly just-introduced `std::string_view` are the right way to handle strings. We can spare the bytes now.
Re:Massacring C Pointers by minipci1321 on Tuesday June 26, 2018 @02:26PM (#17402918)
> Raw null terminated strings are just a bad idea.

Absolutely agree. Scanning the memory until "we find it", potentially crossing boundaries between segments of memory with different characteristics (caching etc.) just doesn't seem right in general, and if I recall, some CPUs even used to have published errata related to that.

> std::string` and the bafflingly just-introduced `std::string_view` are the right way to handle strings.

I'd even go straight to custom implementation of Hollerith strings. Literals have lengths known at compile time, protocols would either carry the lengths alongside the strings, or be trusted (to have good strlen behavior) until they do, composite strings would compute the length out of the components, etc. This doesn't seem too complex to do, looking from from my bell tower, but I know many people here would frown upon mentioning C++ in the context of embedded development (my area).

Re:Massacring C Pointers by _kst_ on Tuesday June 26, 2018 @06:15PM (#17404395)
I've mentioned elsewhere that strncpy is not a "safer" strcpy.

Even if it were, there's safety and there's safety. A function (like strncat, for example) that quietly truncates your data if it's too long isn't necessarily better than one that quietly ignores array overruns. Consider what happens if "rm -rf $HOME/tmpdir" is quietly truncated to "rm -rf $HOME/"

Re:Massacring C Pointers by GlitchMr on Tuesday June 26, 2018 @06:40AM (#17399385)
strncpy is not safe strcpy, for any value of safe. Period. str in the name is really misleading as it's not really a string function to begin with.
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:18AM (#17398292)
> If the input 't' doesn't have a null terminator

Then it's not a string.

Re:Massacring C Pointers by leodeid on Tuesday June 26, 2018 @02:31AM (#17398350)
Sure, but it is still a valid 'char *' :)
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:43AM (#17398400)
That's something C's type system doesn't check for. If you want protection for this case, use C++ or any other higher-level language instead.
Re:Massacring C Pointers by nwmcsween on Tuesday June 26, 2018 @02:56AM (#17398445)
Well to be pedantic C++'s type system doesn't check for that either it just passes around a size_t and char *.
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @03:06AM (#17398487)
I was referring to std::string, which is what you should be using if you're handling textual data natively.
Re:Massacring C Pointers by nwmcsween on Wednesday June 27, 2018 @04:01AM (#17406511)
The implementation of std::string is 99.999% of the time struct { char *s; size_t len; }, which has nothing to do with an actual type.
Re:Massacring C Pointers by gmueckl on Tuesday June 26, 2018 @07:04AM (#17399498)
And it's even possible to use std::string as a buffer for binary data including NULs. I won't recommend it, but it works.
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @12:08PM (#17401762)
I'd suggest using a std::vector of byte-sized integers for clarity, though there's nothing wrong from a standards point in using a std::string.
Re:Massacring C Pointers by skykooler on Tuesday June 26, 2018 @12:57PM (#17402222)
Also, it allocates ints x and y, saves a value to y, copies it to x, and only uses x from then on. y is entirely redundant.
Re:Massacring C Pointers by LukeShu on Tuesday June 26, 2018 @01:58AM (#17398223)
Besides stylistic (dang, that code is hard to read), efficiency (it scans through s twice), and safety (it will do bad things if the combined length is > 100) concerns, it's also just plain wrong:

It returns a pointer to memory on the stack; but the stack will shrink when the function returns, and that piece of memory will get re-used. Which is consistent with the article's later claim that "I don’t think he understands the call stack."

Re:Massacring C Pointers by oddity on Tuesday June 26, 2018 @02:06AM (#17398254)
From a purely safety-minded perspective, this function has a hidden bound of r[] past which the function becomes unsafe, but it does not check. Not only does it not check, the design of the function means that there is no possible way for it to check safely. s and t are both pointers to characters? How long are the strings they might represent supposed to be? Who knows? This code is incredibly reckless and it's dangerous to introduce students to such carelessly designed examples when first impressions will define how they program for a few years or so.

Ok, so it's reasonable to think that this is supposed to be a teaching example and that considering these concepts might be a bit too early in the process. This leads into the second problem and one that is more subjective: this code is incredibly dense and relies on enough quirks of C that it's almost never going to be clear to a beginner reader what they are supposed to take away from it. It's maybe useful as a quiz question on C syntax and semantics, but there are enough barriers to understanding what the code is supposed to do that the amount of explaining the text would need to do to describe what the code is doing is most likely prohibitively long. Instructive examples should be unambiguous in what they are trying to show, otherwise students will be confused and potentially conflate issues in a way that is difficult to untangle later.

Edit: Ha! I spent so long looking at the first half of the function I totally missed that it was returning r! So, not only does this code have minor issues here and there from its careless implementation, it has a fundamental flaw that, if it were to work, would do so only by accident. I can only imagine that a student might walk away from this example thinking that C functions can return arrays and possibly misunderstand scoping in C.

Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:15AM (#17398281)
I'm sorry, but I don't think you've quite gotten the reasons why it was lambasted:

> Not only does it not check, the design of the function means that there is no possible way for it to check safely. s and t are both pointers to characters? How long are the strings they might represent supposed to be?

strlen

> This leads into the second problem and one that is more subjective: this code is incredibly dense and relies on enough quirks of C that it's almost never going to be clear to a beginner reader what they are supposed to take away from it.

Most "nice" C functions are much terser than this.

Re:Massacring C Pointers by oddity on Tuesday June 26, 2018 @02:19AM (#17398296)
strlen is not safe and the design of this function does not permit it to ever be safe. That is my point.

Most "nice" C functions are also not used as teaching examples. There's a difference in how one writes C code for production use and instructive use.

Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:25AM (#17398318)
I take it you come from a higher level language, where null termination would seem risky. In C, however, strlen is considered safe (as opposed to say strcpy, strcat, etc. which do have "safe" replacements). As for terse examples being given to beginners, here's the example The C Programming Language gives for strcpy:

    void strcpy(char *s, char *t) {
    	while ((*s++ = *t++) != '\0') ;
    }
Re:Massacring C Pointers by ChrisSD on Tuesday June 26, 2018 @03:11AM (#17398513)
C11 introduced strnlen_s, a "safe" replacement to strlen.
Re:Massacring C Pointers by beefhash on Tuesday June 26, 2018 @01:04PM (#17402284)
strnlen_s is in Annex K, which is an optional part of the standard. Incidentally, no libc in widespread usage has implemented it.
Re:Massacring C Pointers by ChrisSD on Tuesday June 26, 2018 @02:07PM (#17402783)
True. The non standard strnlen is however more widespread. Microsoft implements strnlen_s in terms of strnlen like so.

    _String == 0 ? 0 : strnlen(_String, _MaxCount)
Re:Massacring C Pointers by colejohnson66 on Tuesday June 26, 2018 @09:47AM (#17400524)
What’s safer about it? Doesn’t `strlen' just scan until the null character, then go

    nullCharPtr - str
? Is there something unsafe with that?
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @11:12AM (#17401161)
strnlen_s takes a parameter for the maximum number of characters to scan. This way, it won’t overflow the buffer you provide it if there’s no trailing null byte.
Re:Massacring C Pointers by ChrisSD on Tuesday June 26, 2018 @10:11AM (#17400708)
What if there is no null?
Re:Massacring C Pointers by slrz on Tuesday June 26, 2018 @05:25PM (#17404127)
Then you probably shouldn't feed it to the str* functions at all. Use the mem* functions instead.
Re:Massacring C Pointers by ChrisSD on Tuesday June 26, 2018 @05:55PM (#17404303)
That's the same as saying you should never use the str* functions. Nothing in C guarantees a string must have a null byte. As you're well aware, C-strings aren't a separate type that's distinct from char arrays.

Sure you can say that good programmers will always ensure a C-string is a C-string but there's decades of programming history that shows that's not true in practice.

Re:Massacring C Pointers by saagarjha on Wednesday June 27, 2018 @02:49AM (#17406223)
Then use a language other than C that doesn't shoehorn string handling into char , as I've mentioned in other comments. If you're have a char with no terminating null byte and you hand it to a function with "str" and its name, you're going to have a bad time.
Re:Massacring C Pointers by a1369209993 on Tuesday June 26, 2018 @04:10PM (#17403619)
If there isn't a NUL, then you're asking for a property relevantly distict from length of a datum relevantly distinct from a C-string. Use `strchr(s,0x00)` or `memchr(s,0x00,zs)`.
Re:Massacring C Pointers by toopsss on Thursday June 28, 2018 @02:44PM (#17419414)
You haven’t read K&R C I’m going to guess. Terseness is a traditional component of C and UNIX (cf creat). I don’t care to debate the merits but that is a fact. Most C books in the 80s were written in this style. Furthermore in the days or micros code was generally terse for many and varied reasons. There were people typing code on membrane keyboards.
Re:Massacring C Pointers by saulrh on Tuesday June 26, 2018 @02:00AM (#17398233)
Not a C programmer, so I'm sure I'm missing a ton of more subtle issues, but some of the immediate questions to ask:

* What happens if s or t are longer than 100 characters?

* What happens if s and t are both longer than 50 characters?

* What happens if no element of s == '\0'? How about t?

* r is allocated on the stack. What happens to the memory pointed at by r when you call another function after calling combine? Say you wanted to combine three strings; could you call combine twice to build up the result?

Re:Massacring C Pointers by daemin on Wednesday June 27, 2018 @06:30AM (#17407083)
For the first two points, you end up overwriting the stack and most likely crash. This is how security holes were/are made.

For the third point, you have two main ways of storing strings in general. The first was called pascal style, where the length is stored first in either a byte or two and then the string data with no (or an optional) terminating null character (the '\0'). The second is referred to as c style, and is done by storing the string in a memory and denoting the end of the buffer by a null character.

The c-style enables certain nicer looking c code with loops and such, but it is more dangerous and potentially expensive if you recompute the length all the time. You can always mix and match the way you store the string, such as in C++ where the string class stores a length as well as a null terminated string in a buffer.

For your forth point, yes, you end up corrupting the string leading to more crashes and/or it can be used as an entry point to screw around with your program's stack.

Re:Massacring C Pointers by grenoire on Tuesday June 26, 2018 @07:28AM (#17399615)
Back in the day it was pretty much granted you ended your strings with a \0.
Re:Massacring C Pointers by konstmonst on Tuesday June 26, 2018 @02:19AM (#17398294)
There are multiple problems with that code: 1) return(r) He is returning pointer to temporary! When you declare and initialize variables on stack after that function, they will overwrite memory pointed by r. 2) He is assuming size of string pointed by s(including 0) is less than 100 and also the combined sizes of s and t are less than 100. Stack Overflow! 3) Not an errors, but would not pass my code review: Inconsistent Variable declaration, using signed integer for loop where you don't need signed. Also I would have used strcpy 2 times, or loop 2 times, not two different ways. Beside that, you should not use strcpy, but strncpy to avoid stack overflows.
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:32AM (#17398353)
> Inconsistent Variable declaration

How so?

> I would have used strcpy 2 times

You can't do this, because strcpy doesn't give you the length of the string you copied, which is necessary to put the trailing null byte.

Re:Massacring C Pointers by scatters on Tuesday June 26, 2018 @04:51AM (#17398888)
strcpy writes a trailing zero.

> The strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to by dest.

Re:Massacring C Pointers by saagarjha on Wednesday June 27, 2018 @02:46AM (#17406219)
Yup, I'm aware of that, but the author of that code sure isn't ;)
Re:Massacring C Pointers by badrabbit on Tuesday June 26, 2018 @02:05AM (#17398252)
There are a few bugs in it. It returns something on the local functions stack (r) which gets torn down after return. It uses the insecure strcpy allowing the caller to stack overflow the destination. It increments t in the loop after dereferencing it which only changes the value pointed to by t. x and y are improperly initialized, it null terminates r without checking for x's value(again a buffer overflow) and there is no upper bound limit checking in the for loop(second for() parameter). I'm sure there are other bugs too but I haven't looked at it too closely.

But,yeah... It's a massacare.

Re:Massacring C Pointers by Figs on Tuesday June 26, 2018 @03:20AM (#17398542)
> It increments t in the loop after dereferencing it which only changes the value pointed to by t.

This is wrong. Postfix increment has a higher precedence than the dereference operator.

Re:Massacring C Pointers by badrabbit on Tuesday June 26, 2018 @02:40PM (#17403032)
Are you sure? I'll need to test this. Always thought postfix increment and decrement applied after everything else was evaluated,and that's why prefix versions exist.
Re:Massacring C Pointers by Figs on Tuesday June 26, 2018 @05:49PM (#17404265)
Yes. Here is a table that lists operator precedence in C: https://en.cppreference.com/w/c/language/operator_precedence

Note that postfix increment is at the highest precedence and the dereference operator is at the second highest precedence.

The postfix operators behave in a bit of an interesting way; the expression "t++" evaluates to just "t" but increments t as a side effect. Consequently, it appears to update t after the expression it is part of, even though it is one of the first things evaluated.

If you actually test the function, you will find that it works provided that you make r static or global instead of a local stack variable -- and, of course, carefully mind the restriction on the length of the input strings to avoid overflowing the buffer.

Re:Massacring C Pointers by badrabbit on Wednesday June 27, 2018 @12:47PM (#17409673)
Thank you! I learned something new.
Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:16AM (#17398285)
> x and y are improperly initialized

How so?

Re:Massacring C Pointers by badrabbit on Tuesday June 26, 2018 @02:25PM (#17402912)
I said that because I forgot the strlen was there. I was wrong.
Re:Massacring C Pointers by userbinator on Tuesday June 26, 2018 @07:28AM (#17399622)
I know there are already plenty of comments about the problems but might as well give mine too...

In one sentence: this guy managed to reinvent a variant of strcat and get it completely wrong.

Here's a somewhat better version:

    char *combine(char *s, char *t) {
        size_t m = strlen(s), n = strlen(t);
        char *ret = malloc(m + n + 1);
        if(ret) {
            memcpy(ret, s, m);
            strcpy(ret + m, t);
        }
        return ret;
    }
Re:Massacring C Pointers by jmts on Tuesday June 26, 2018 @08:35AM (#17400031)
You already know the length of t. strcpy is inefficient when another memcpy will do.
Re:Massacring C Pointers by jwilk on Tuesday June 26, 2018 @07:53AM (#17399751)
Minor bug:

If s and t are very long and overlapping, m + n could wrap around.

Re:Massacring C Pointers by TomVDB on Tuesday June 26, 2018 @01:54AM (#17398201)
It returns the result r, which is a temporary, stack allocated array that becomes undefined by the time your return from the function. That’s the worst violation.

Furthermore, r is a fixed 100 bytes. There is no overflow checking whatsoever.

Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @01:50AM (#17398186)
The C syntax is a bit too dated for me, but I think it returns a pointer to a stack buffer (which is capped at 100 bytes, so it could overflow) and goes through s twice? Plus it's not concise at all.
Re:Massacring C Pointers by flukus on Tuesday June 26, 2018 @02:33AM (#17398355)
> Can someone please elaborate why it is bad? Are their any good resources to fill gaps in my knowledge?

If you're actually learning C and writing programs in it then the two best things to do would be turn compiler warnings up to 11 and run valgrind often. So you want -Wall and -Wpedantic when you compile and if possible run with valgrind as part of your build script or run it with your tests, just run it often (the longer between runs the harder it is to track back to which change you made).

Re:Massacring C Pointers by saagarjha on Tuesday June 26, 2018 @02:47AM (#17398415)
Or -fsanitize=address, if your OS does not support Valgrind.
Re:Massacring C Pointers by aaronmdjones on Wednesday June 27, 2018 @11:21AM (#17408862)
Note that you forgot '-Wextra', but even `gcc -Wall -Wextra -Wpedantic` doesn't enable all diagnostics (there are some 40 additional arguments to provide, depending on GCC version); so what you really want is a recent version of Clang and '-Weverything'.
Re:Massacring C Pointers by archgoon on Tuesday June 26, 2018 @03:04AM (#17398479)
Putting my CR hat on:

   1 char *combine(s, t)
   2 char *s, *t;
   3 {
   4
   5   int x, y;
   6   char r[100];
   7
   8   strcpy(r, s);
   9   y = strlen(r);
  10   for (x = y; *t != '\0'; ++x)
  11     r[x] = *t++;
  12 
  13    r[x] = '\0';
  14
  15    return(r);
  16
  17 }
There are several critical memory errors here.

1) The function is returning the address of a local variable. This alone makes this function rubbish.

2) The pointers s and t are unknown length, we have no guarantees that concatenating them will fit in a 100 character array. Also, he probably should be using malloc to dynamically allocate the space.

3) Use of the function strcpy rather than strncpy. He should have measured the length of s first, and then use strncpy if s was longer than 99 characters (don't forget the null terminator!). Then, rather than using a for loop, call strncpy again to copy the rest into a safe buffer size (the for loop is rather silly). The reason for this is that strcpy will cheerfully start copying past the array 'boundaries', and in this case, since he's copying into a local variable on the stack, is setting himself up for a Remote Code Execution attack if this ever gets untrusted input.

So those are the critical errors. These tie directly into why Geoff argues that the author doesn't understand the stack.

So let's, for educational purposes, go into this. We're going to go a bit into the weeds here. Sorry about that. This would be easier with a whiteboard. :)

When you fire up a program, the programs machine instructions get copied into memory, let's pretend at the memory location 0x1000. Far away from that code, at the highest memory values (more complicated on modern virtual systems, but hey, let's go back in time here :) ), the computer keeps track of a location called the stack pointer.

I'm going to put forward 3 diagrams now. Please forgive any off by one errors.

  (Diagram a)
    Registers
    A 0
    B 0
    C 0
    SP 0xffff
    PC 0x1000

    Address  Mnemonic   DATA
  PC0x1000   MOV 1, A   0x00 0x01 0x01 0x01
    0x1004   MOV A, C   0x00 0x01 0x03 0x01
    0x1008   PUSH 3     0x01 0x00 0x00 0x03
    ...      ...
    ...      ...
    ...      ...
    0xfffc   XXXXXXXX   0x00 0x00 0x00 0x00 <-- Stack Pointer is here
The program starts at 0x1000, then after executing the first two move (MOV) instructions, the state of the world becomes as follows

  (Diagram b)
    Registers
    A 1
    B 0
    C 1
    SP 0xfffe
    PC 0x1000

    Address  Mnemonic   DATA
    0x1000   MOV 1, A   0x00 0x01 0x01 0x01
    0x1004   MOV A, C   0x00 0x01 0x03 0x01
  PC0x1008   PUSH 3     0x01 0x00 0x00 0x03
    ...      ...
    ...      ...
    ...      ...                     v------\
    0xfffc   XXXXXXXX   0x00 0x00 0x00 0x03 ^-- Stack Pointer is here
When you have code like

  void function() {
     int a = 5;
     int b = 2;
     return a;
  }

  void main() {
    return function();
  }
It'll get turned into something like (I've set a 'break point' at 0x2010)

  (Diagram c)
    Registers
    A 5
    B 0
    C 0
    SP 0xfff8
    PC 0x2010

    Address  Mnemonic      DATA
    # Main starts here
    # (Note, in C, there is actually code that gets executed before this)
  PC0x1000   PUSH 0x1008   # We want to remember where to return to, so we push it to the stack.
    0x1004   JMP  0x2000
    0x1008   EXIT A        # In this implementation of C, the A register will propagate return values
    ...      ... 
    # Function 'function' is here
    0x2000   PUSH 5        # Local variables go on the stack.
    0x2004   PUSH 2
    0x2008   MOV [SP+2], A # Locally, we refer to local variables by
                           # offsets to the stack pointer, so if this function
                           # were to call itself, the stack would keep growing down
                           # but these values would be good.
    0x200c   MOV SP+2, SP  # Reset the stack before returning
  PC0x2010   JMP #SP       # Made up notation. Look at the value of the stack pointer, pop it, and jump to it.
                           # in x86, this is kinda what RET does.
    ...      ...
    ...      ...           ...
    ...      ...           ...         
    0xfff8   XXXXXXXX      0x00 0x00 0x00 0x00SP  
    0xfffc   XXXXXXXX      0x02 0x05 0x10 0x08
Okay! So with the above diagrams in mind, let's recap what goes on the stack. Local variables and return addresses. Each time a function gets called it moves the stack pointer down[1] (to lower memory addresses) to make room for local variables. So after you return from that function, and then call another function (or heck, the same one) that pointer you have that was supposed to be the concatenated string is now going to have it's values overwritten.

Furthermore, if the input strings are longer than expected, than they can overwrite values on the stack itself, including the return addres, causing your program to jump to some (if you're lucky) random location in memory.

Honestly, some of the best ways to get intuition for how the stack works, and the things that can go wrong, are CTFS at overthewire.org.

Also, https://microcorruption.com/

http://overthewire.org/wargames/bandit/

http://overthewire.org/wargames/leviathan/

[1] Sorry, 'down' means lower memory addresses, even though the displays of memory layouts always have lower memory addresses "up". :(

Re:Massacring C Pointers by jmts on Tuesday June 26, 2018 @02:31AM (#17398349)
Most of these points are covered by the other comments. As a C programmer professionally, I'll go into a little more depth, and offer an alternative implementation for comparison.

The function in question:

    char *combine(s, t)
    char *s, *t;
    {
          int x, y;
          char r[100];

          strcpy(r, s);
          y = strlen(r);
          for (x = y; *t != '\0'; ++x)
               r[x] = *t++;
          r[x] = '\0';
          return(r);
    }
1. The array 'r' is allocated on the stack, and returned from the function. This is bad because 'r' goes out of scope as soon as the function is returned. This function returns a pointer to memory with essentially unknown contents.

2. The array 'r' is allocated at a fixed size. This is okay if you know ahead of time that you know this size, however for this function we don't know the lengths of s and t, so the odds that we are allocating the right amount of memory is slim.

3. As a result of points 1 and 2, the strcpy and loop may cause a buffer overflow. This is a class of bug where it is possible to overwrite memory that should be unavailable to us. In this case, if the combined lengths of s and t happen to be equal or greater than 100 characters, we will be overwriting memory that does not belong to r, corrupting it, and potentially crashing the program. This may additionally be as security risk, as buffer overflows can be exploited to execute malicious code.

4. There are no checks to see whether s and t are valid pointers. If they are NULL then the function would generate a segmentation fault. This is a check that is often ignored in cases where it is deemed to potentially hinder performance if the function is used frequently.

saulrh also mentions the case where s or t are not NUL-terminated. This is often considered to be a pre-condition of the function in C, suggesting that providing the function strings that aren't NUL terminated is an issue for the user.

For some comparison the following would be my first cut at the same function. Note I the comments are only for illustrative purposes. I'd omit them in actual code.

    char* combine(const char *s, const char *t)
    {
        size_t slen, tlen;
        char *str;
        
        // optional checks for validity (point 4)
        if (NULL == s || NULL == t)
        {
            return NULL;
        }
        
        // get lengths of s and t, to calculate allocation size
        // also save them to use with memcpy later
        slen = strlen(s);
        tlen = strlen(t);
        
        // allocate to heap (point 1)
        // allocate correct size (point 2)
        str = malloc(slen + tlen + 1);
        if (NULL == str)
        {
            return NULL;
        }
        
        // use memcpy since we already know the size
        memcpy(str, s, slen);
        memcpy(str + slen, t, tlen);
        str[slen + tlen] = '\0';
        
        return str;
    }
Re:Massacring C Pointers by huhtenberg on Tuesday June 26, 2018 @05:56AM (#17399205)

    if (NULL == s)
Ouch.

I haven't seen a single case where this abomination actually helped catching the fearsome 'if (s = NULL)' typo. One needs to be a sloppy typist, not paying attention to what they write, not proof-reading the code before committing and ignoring compiler warnings for this disaster of a notation to be even remotely justified.

Re:Massacring C Pointers by TickleSteve on Tuesday June 26, 2018 @07:37AM (#17399668)
Yoda conditions are protection against an issue that no longer exists in most compilers.

(https://en.wikipedia.org/wiki/Yoda_conditions)

Re:Massacring C Pointers by huhtenberg on Tuesday June 26, 2018 @08:11AM (#17399879)
Precisely! Yet they still pop up and ruin readability of the code just the same.
Re:Massacring C Pointers by maskros on Tuesday June 26, 2018 @06:50AM (#17399433)
Better yet, just use the fact that NULL is false and get shorter, cleaner, and safer code.

  if (!s)
Re:Massacring C Pointers by TickleSteve on Tuesday June 26, 2018 @07:39AM (#17399673)
God no... explicit is better than implicit.

Your statement there just does not read correctly, always have the condition explicit.

Re:Massacring C Pointers by 08-15 on Wednesday June 27, 2018 @09:05AM (#17407802)
Why? It reads nicely as "if there is no string", can't get more explicit than that.
Re:Massacring C Pointers by jmts on Monday July 02, 2018 @02:54AM (#17439759)
The biggest problem I have with this style is that it is inconsistent across variable types and provides no insight into the actual operation being performed without further knowledge. My personal experience is that my ability to read and comprehend code quickly is dependent on the context and patterns present within it (among other things). Therefore making the expression explicit as a pattern adds context and therefore increases readability and comprehension.

The statement '!x' tells me nothing about x, just that I'm expecting a given logical value. The semantic meaning of that value however, I have no idea. Odds are it is probably a NULL or 0, but that doesn't help much because (at least in the linux world) they can have different meanings despite having the same logical value. I cannot differentiate between success (!(x == NULL)) or failure (!(x == 0)) without more context.

The statement 'x == NULL' immediately tells me at a glance that I am dealing with a pointer, and therefore I should pay more attention to how it is used. I know I should now be looking for other patterns of safe/unsafe pointer management that are immediately relevant to the function I am reading right now. There is no need for me to look elsewhere and go on some tangent to find out, then to have to recall what I was doing when I come back later. I can immediately start considering the likelihood of segmentation faults, or other memory management issues. x == NULL also tells me that here I am expecting failure of some kind. The context around that condition should then tell me which kind, eg NULL == alloc_thing() vs NULL == find_thing().

Similarly, the statement 'x == -1' tells me I am working with an integer. This can tell me immediately whether I am expecting success (x == 0), failure (x == -1), or that I should make a mental note of more complex possibilities (THING_WORKED == do_thing()).

Code is a narrative. It is documenting the answers to the questions you are asking while writing it, and it should answer the questions that someone else is asking while reading it. When somebody asks you to explain something, the least helpful thing you can do is answer a plain "yes" or "no". It is better to give context and answer further related questions before they need to be asked. Readability and comprehension of code are no different - any given programmer is asking questions of the code. The more questions they have to ask, the longer it will take them to discover the answers in order to understand the code. Context helps. Shorter is not always better.

Re:Massacring C Pointers by toopsss on Thursday June 28, 2018 @02:48PM (#17419450)
What is not explicit about that statement? The evaluation of the variable is well defined in a Boolean context.

If you have to write “== true” everytime that’s your own idiosyncratic hangup. It has nothing to do with explicitness, it’s just redundant. The true test was explicit the moment you wrote if (

Re:Massacring C Pointers by jmts on Tuesday June 26, 2018 @07:59AM (#17399786)
And thus in your disgust you have actually given a case where this convention might actually save somebody time. If it happens to end up being your own time, you ought to be thankful for it. You certainly don't want it to be wasting your customer's time.

I actually recently saw an 'if (x = 0)' or similar get caught in review recently. Time pressure increases, tests are rushed, and authors proof-read the code in their head, and not what's on the screen. These things do happen, and if your personal style preferences get in the way of using a simple trick to save your own time at best case, or multiple other people's time at worst, then you might wish to reconsider your preferences - even if it is at 1 in 1000 odds.

Re:Massacring C Pointers by huhtenberg on Tuesday June 26, 2018 @08:18AM (#17399903)
For all intents and purposes this is one of many typo classes that just happens to have a cute "antidote", and which also has been all but rendered pointless by the existence of respective compiler warnings.

I've seen weirder stuff get through the reviews and compile cleanly, something like "f,()". Typos happen, but (a) it's not a good enough reason to make the code less readable (b) if the code is prone to this sort of errors, just pay closer attention to them during the review phase. Hedging against a single exotic type of mistake that virtually never happens at the expense of code readability is unacceptable.

Re:Massacring C Pointers by macintux on Tuesday June 26, 2018 @03:02PM (#17403199)
I strongly disagree that this impacts code readability, except perhaps for a beginner, but stretching a beginner’s mind a bit with a concept that’s not exactly a major challenge to understand hardly seems like a crime against humanity.
Re:Massacring C Pointers by caf on Tuesday June 26, 2018 @02:48AM (#17398420)
You can avoid the need for setting the null terminator explicitly by using memcpy(str + slen, t, tlen + 1) as the second memcpy.
Re:Massacring C Pointers by jmts on Tuesday June 26, 2018 @02:58AM (#17398452)
And this is why we have code review ;)