News for hackers. Stuff that matters.
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 ;)