13

I bought this book called "Writing Solid Code".

In the first chapter, there's this code:

while(*pchTo++ = *pchFrom++)
   NULL;

I really don't get what that NULL does in that loop.

sth
  • 222,467
  • 53
  • 283
  • 367
ksp0422
  • 349
  • 1
  • 4
  • 14
  • 1
    It separates the semicolon from the loop condition. (It's dead code.) – Daniel Fischer May 06 '13 at 14:11
  • 22
    And such a book is called "Writing Solid Code"? – Leo Chapiro May 06 '13 at 14:12
  • 12
    Whether such practice is desirable is open to debate, but a book that uses such a construct without devoting at least a short paragraph to explaining the motivation strikes me as not being a good book... – R.. GitHub STOP HELPING ICE May 06 '13 at 14:15
  • 1
    @duDE there's really nothing fragile about that loop, it just seems that the author doesn't know how to explain what he's doing or why. – Ian Stapleton Cordasco May 06 '13 at 14:15
  • `while(*pchTo){pchTo++ = *pchFrom++}` seems a better idiom (if initialized properly) – fotanus May 06 '13 at 15:07
  • 3
    @fotanus, what you wrote has a parse error (missing semicolon), type error (`pchTo++` is `char *` while `pchFrom++` is `char` (for example)) and a logical error (it should have been `while (*pchFrom)`). I can't believe you managed to introduce three errors in such a short piece of code. – Shahbaz May 06 '13 at 15:11
  • 1
    @Shahbaz neither do I. Seems I did a nice job creating the worst line ever, can't think in how to introduce a new error on it. – fotanus May 06 '13 at 15:18
  • 1
    @fotanus, it seems like the code in the book _was_ solid after all ;) – Shahbaz May 06 '13 at 15:22
  • Well, the book did win a Jolt Productivity award from Dr. Dobbs in '94. Also it's part of StackOverflows list of influential books: http://stackoverflow.com/questions/1711/what-is-the-single-most-influential-book-every-programmer-should-read – austin May 06 '13 at 15:26
  • it meant NOP(NO OPERATION). – BLUEPIXY May 06 '13 at 15:44
  • @Shahbaz: One more error(?): Since the copy statement is now executed *after* the loop guard check instead of before it, it doesn't copy the null terminator. Incidentally, this is why I dislike oldskool style C code, it's so hard to check for correctness. – Joren May 06 '13 at 17:30
  • @joren, we could make a contest out of this ;) – Shahbaz May 06 '13 at 21:21

4 Answers4

6

The author could have written an entirely empty while loop just like you can write a body-less for loop. That would have looked like:

while (*pchTo++ = *pchFrom++);

/* or */

while (*pchTo++ = *pchFrom++)
    ;

Both of which might look confusing for some people so they added the NULL to give it a body and make it less confusing.

Edit

Note you can do the same with a for loop:

for (head = list->head; head->next; head = head->next);
/* or */
for (head = list->head; head->next; head = head->next)
    ;
/* or */
for (head = list->head; head->next; head = head->next)
    NULL;

And all that will do is traverse a list by going to the next element while the next element is not NULL

Ian Stapleton Cordasco
  • 26,944
  • 4
  • 67
  • 72
  • 3
    I don't see how that's "less confusing". Having an expression whose value is unused and which has no side effects seems a lot more confusing. My evidence? The fact that OP had to ask this question. – R.. GitHub STOP HELPING ICE May 06 '13 at 14:14
  • It's less confusing for people who expect a body and have never seen a loop without one, or at least I'm guessing that's why the `NULL` was placed there. It really just looks ugly to me and when I have a list object (like above) that doesn't have a `tail` member, I usually have a convenience function that uses the for loop I demonstrated above just to get it. On an editorial note: people who don't include a `tail` member in their struct really should reconsider their design. – Ian Stapleton Cordasco May 06 '13 at 14:21
  • Anyone who (intentionally) puts a `;` in the same line as a `for` or `while` statement deserves (at least) a solid whack on the head. [Keith's answer](http://stackoverflow.com/a/16401457/1711796) briefly touches on possible confusion. On a side note, another option many people prefer is using curly braces (`{}`) in some form or another. – Bernhard Barker May 16 '13 at 17:53
3

That loop copies a string. The NULL is just there to provide a loop body.

austin
  • 5,816
  • 2
  • 32
  • 40
  • 2
    That's just so confusing... especially for a reference book. He could have simply left the while body empty with two curly braces: `while(...) { }`. Or even do the real thing: `while(*pchFrom){ *pchTo++ = *pchFrom++; }`. This is way more clear.. – Gui13 May 06 '13 at 15:14
  • 1
    @xgbi: That doesn't copy the null terminator. – Joren May 06 '13 at 17:26
3

It's likely inspired by the syntax of Ada's null statement:

while condition loop
    null;
end loop;

Ada uses the null keyword both for the null pointer constant and for the null statement (and a few other things).

It does have some advantages over C's null statement, which is just a semicolon. In particular, it's much more explicit. A fairly common error in C code is to insert an accidental null statement by adding a semicolon, particularly among inexperienced C programmers who haven't yet worked out where semicolons are necessary and where they aren't:

while (condition);
{
    /* statements */
}

Without the semicolon, the statements are controlled by the while loop. With it, the body of the while loop is empty, and the loop is likely be infinite if the condition has no side effects.

On the other hand, if you really want a null statement, using just a semicolon can leave the reader wondering if something was unintentionally left out.

In C, NULL is a macro that expands to an implementation-defined null pointer constant. The author is using it here:

while(*pchTo++ = *pchFrom++)
    NULL;

as a kind of null statement -- which actually works, because an expression followed by a semicolon is a statement expression in which the statement is evaluated for its side effects. Since it has no side effects, it does nothing; it acts just like a real null statement:

while(*pchTo++ = *pchFrom++)
    ;

Another equivalent form:

while(*pchTo++ = *pchFrom++)
    42;

In my opinion, this is well intended but a bad idea. It's easily recognizable to those few of us who happen to be familiar with both C and Ada, but most experienced C programmers will look at it and wonder what the heck that null pointer constant is doing there.

It's not quite as bad as defining a set of macros to make C look like another language's syntax:

#define IF if (
#define THEN )
#define BEGIN {
#define END }
#define ELSE } else {

but it's in the same spirit.

My advice: Don't do this kind of thing. If you want your C code to be easily understandable by readers who know C, write idiomatic C code; don't invent clever tricks to make it look like something else. Null statements can be confusing, leading the reader to wonder if something was left out accidentally. The best solution that, IMHO, is to use a comment:

while(*pchTo++ = *pchFrom++) {
    /* empty body */
}
Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 2
    I find `while(*pchTo++ = *pchFrom++){}` equally clear. The empty block without even the space to put something there signals "do nothing" is intentional. – Daniel Fischer May 06 '13 at 15:24
2

This construct provides a good way to place a breakpoint when working with a debugger. Some IDEs did not let me place a breakpoint on a loop and I had loops with empty bodies. If you have a statement in your loop body, it is easier to place a breakpoint on it, even if that statement is useless.

Otherwise, as it has already been said, it should do exactly the same thing than a body with an an empty body.

Jehan
  • 746
  • 5
  • 13