62

I had an annoying bug, where I forgot to write do in a do ... while loop.

int main() {
  int status=1;
  /*do*/ {
    status = foo();
  } while (status);
}

Why does this still compile and run? It seems to me that the compiler should reject this as nonsensical or at least raise a warning (I have -Wall in my compiler options). I'm using C++11.

From what I could tell, the braced code runs { ... } and then the programme checks the condition in the while clause ad infinitum.

D. Pardal
  • 6,173
  • 1
  • 17
  • 37
thedoctar
  • 8,943
  • 3
  • 20
  • 31
  • 13
    Your own explanation seems to be correct. – mkrieger1 Aug 02 '20 at 22:04
  • 8
    I get an error when I compile this. It complains that the variable `status` is not declared. – Barmar Aug 02 '20 at 22:05
  • 28
    What you've written is a braced block followed by a `while` loop with an empty body. – Barmar Aug 02 '20 at 22:06
  • 5
    While there was no warning, since this could be perfectly valid code, I am pretty sure that a static code analyzer such as clang-tidy would have warned you that the checked variable is not updated in the loop body. Note also that `-Wall` enables by far not all warnings, even if it is so unfortunately named. The combination `-Wall -Wextra` is a good base and maybe there are even mor warnings that are appropriate for youe problem – n314159 Aug 02 '20 at 23:25
  • 1
    [I ran into a *"while-while"* loop](https://sudo-coding.blogspot.com/2018/12/the-while-while-loop.html#more) like this recently. The diagnosis is the same. – Digital Trauma Aug 03 '20 at 17:57
  • 1
    You should format your code with something like clang-format, and it will help you see the mistake. – cigien Aug 08 '20 at 02:04

3 Answers3

92

The code you wrote is still valid without do, but does something different, as you already correctly noted. Let me rewrite it slightly to show how it is interpreted:

int main () {
  int status;

  { // anonymous braced block that just creates a new scope
    status = foo();
  }

  while (status) {
    // empty loop body
  }
}

A stand-alone block like that does have its uses, for example to utilize RAII - it could contain a local variable with an object whose destructor frees some resource when it goes out of scope (for example a file handle), among other things.

The reason that the while (status); is the same as while (status) {} is because you are allowed to put either a single statement or a block, and ; is a valid statement that does nothing.

And writing something like while (someVariable); isn't even nonsensical in general (although of course in this case it is) because it is essentially a spinlock, a form of busy waiting - it would leave the loop if another processor core, some I/O component or an interrupt would modify the value of someVariable so that the condition is no longer fulfilled, and it would do so without any delay. You would probably not write such code on a desktop platform where "hogging CPU" is a bad thing (except in specific scenarios in kernel-mode code), but on an embedded device like a microcontroller (where your code is the only code that runs) it can be a perfectly valid way of implementing code that waits for some external change. As pointed out by Acorn in the comments, this would of course only make sense if someVariable were volatile (or otherwise non-predictable), but I'm talking about busy loops on a variable in general.

CherryDT
  • 25,571
  • 5
  • 49
  • 74
  • 6
    "*writing something like `while (status);` isn't even nonsensical*" It is nonsensical as written, because if `status` is truthy, then it is UB. Optimizers may assume `status` is zero, remove the loop and start running wild on the code on `foo()` and the rest. It is best to say that such a loop *must* do something meaningful, or `status` be marked `volatile`, or be an object with an `operator bool`, etc. – Acorn Aug 03 '20 at 00:34
  • Yes, of course. I was talking about code like that in general. `status` would have to be `volatile` if it were a variable (or an I/O memory reference). My examples would not make sense with a _local_ variable `status` anyway, so in this _particular_ case it _is_ nonsensical. – CherryDT Aug 03 '20 at 00:37
  • Yeah, I pointed it out because beginners like OP are unlikely to know how low level code is written, so they may not get that it does not make sense as written. Perhaps writing `while (...expression...);` (it seems italics don't work inside code blocks...) may help. – Acorn Aug 03 '20 at 00:41
  • 1
    I updated the answer. But I meant in particular something that looks like it is useless because it's a variable that is not updated inside the loop (not just _some_ expression like a method call which would appear more logical). – CherryDT Aug 03 '20 at 00:42
  • 2
    `operator bool()` may have side effects too, so it's not like writing `while(someVariable);` needs external change (volatile/atomic) to exit. And `while(someFunction())` can change too – Noone AtAll Aug 03 '20 at 08:59
  • Of course... The answer is not all-encompassing. But the whole thing about the use cases of `while(something);` is a side note (for this reason in small print), no need to bloat it further with details and clarifications. The main question was something else. – CherryDT Aug 03 '20 at 09:06
  • I've written many loops of the style `while(someExpression) ;` and `for(...) ;` where the loop control expressions already contain all the work that needs to be done. Canonical example is the `strcpy()` implementation with `while(*dest++ = *source++) ;` Of course, that's a bad example in way because it's rather cryptic, but especially with `for()` loops, there are many instances where there simply is no reason to use a loop body. – cmaster - reinstate monica Aug 03 '20 at 11:15
  • 2
    Thanks, this was the answer I was looking for. I often have one-line if statements. But I didn't realise you could do it for while loops, and also leave out any statement. If I were designing a language, I'd probably ask for an explicit word like pass to designate "do nothing" in the language and require unbraced while loops to have at least one statement. – thedoctar Aug 03 '20 at 15:15
  • @cmaster-reinstatemonica The missing loop body is not the problem; the non-termination is. The C++ standard doesn't know/care about external interrupts. – Asteroids With Wings Aug 04 '20 at 09:18
  • @AsteroidsWithWings My point was that there are valid reasons to write bodyless loops even when the code is single threaded and not working with hardware that might asynchronously change data. And its such valid usecases for bodyless loops that basically force compilers to not throw a fit when they see the token sequence `; { stuff } while(stuff) ;`. – cmaster - reinstate monica Aug 04 '20 at 09:25
  • @cmaster-reinstatemonica Absolutely – Asteroids With Wings Aug 04 '20 at 09:31
  • 2
    Just to be explicit on the usefulness of the while loop: `while(someVariable)` may have limited use, discussed in other comments, `while(f());` is indeed a very useful construction. Often we need `f` to do some work, and then return true if it needs to be run again, or false if the work is complete. – Cort Ammon Aug 04 '20 at 16:20
  • 2
    @CortAmmon I will normally add an explicit `continue` in situations like this: `while (foo()) continue;`. Makes it easier to spot those instances which are actually an extraneous semicolon on the end of the `while` line. – user7761803 Aug 05 '20 at 14:39
16

The compiler cannot raise an error here since according to section 6.5 of the C++11 standard this is perfectly valid code. In fact there are two flavors of while:

  1. while ( condition ) statement
  2. do statement while ( expression );

statement can be

  • a single statement or
  • a block of statements in curly braces or
  • the empty statement (;)

With this in mind let me format your code how the compiler sees it:

int main () {
  int status;

  { // braced block that just creates a new scope
    status = foo();
  }

  while (status) /* empty statement */;
}

While it might be obvious to a human reader that you meant to loop over the code in curly braces this isn't obvious to the compiler. This has to do with the fact that C++ compilers generally don't look at indentation and line-breaks. An analysis tool taking them into account could warn you that the way you formatted your code doesn't go together with what it is actually doing and correct that for you. That would make the mistake more obvious to you. Or maybe one day we get a language feature which allows us to say explicitly "empty statement". This would allow us to clearly state our intent. Once we have that compilers could issue a warning when the code isn't clear. Until then we have to be careful - C++ is a powerful language but it has a few sharp edges...

BTW you're not the first one who drew wrong conclusions from indentation/line-breaks.

Martin Konrad
  • 1,075
  • 1
  • 10
  • 20
7

Why does this still compile and run?

It doesn't because status is not defined.

It seems to me that the compiler should reject this as nonsensical or at least raise a warning (I have -Wall in my compiler options).

Assuming you define status, it is a valid program. Some compilers or analyzers may generate warnings for infinite loops or for no-op while bodies.

Acorn
  • 24,970
  • 5
  • 40
  • 69