12

An infinite loop with an empty body has undefined behaviour in C++11. I don't know whether it also does in C, so let's say I'm writing embedded firmware in C++11 (I know, unlikely, but bear with me).

If my main were simply a:

while (true) {}

and the rest of the device's functionality were handled by interrupts, what approaches can I take in order to discover whether my implementation makes this loop safe and meaningful? Remembering that, per the standard, an implementation is free to do whatever it wants in this case, including removing the loop entirely.

Assume it's not clearly stated in the implementation's documentation, as I've never seen that.

Or is this a lost cause, and I should hack a workaround?

volatile unsigned int dummy = 0;

while (true) {
   // Make the loop well-defined...
   dummy++;

   // ...with a trivial operation that'll hardly ever even happen
   sleep(42*86400);
}

I recognise that embedded developers historically don't give much thought to this kind of thing, instead assuming a more "down to earth", "common sense" approach from their compiler. But I prefer to code rigourously to standards, to avoid surprises as much as possible.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    Strange you should have questions also :) – The Vivandiere May 15 '15 at 15:03
  • @StraightLine: Occasionally ;) – Lightness Races in Orbit May 15 '15 at 15:04
  • 8
    If this is more than a theoretical question, your embedded CPU almost certainly has an instruction to halt operation until an interrupt comes its way. That would save some battery power (and, from what I gather, make your loop well-defined). – zneak May 15 '15 at 15:05
  • 1
    @zneak: That sounds like the beginnings of a good answer ... :) – Lightness Races in Orbit May 15 '15 at 15:07
  • @zneak It's not the case for every CPU.. – Eugene Sh. May 15 '15 at 15:08
  • I wonder if something like `__asm nop _endasm` will be considered as "side effect" (well, it's out of standard I guess) – Eugene Sh. May 15 '15 at 15:20
  • 1
    Having some MCU experience myself, I agree with @zneak that going to a sleep/low power state is likely the best approach in practice. As for the question in your title, it sounds like the obvious approach is to look at the generated assembly? In most cases, I would expect to just see a single jump instruction (or similar) looping back to itself. – Nicu Stiurca May 15 '15 at 15:20
  • 1
    For the workaround case, a repeated read from a safe `volatile` memory space (like a hardware register) might be a good idea to ensure that the loop body doesn't get optimized away under any circumstances. – Jason R May 15 '15 at 15:21
  • @SchighSchagh: Looking at generated assembly on one given build run doesn't really give you the information you need. Well, unless you're only going to ship that exact resulting binary. :) – Lightness Races in Orbit May 15 '15 at 15:21
  • 1
    You should also be aware that per 1.9p6, values that are neither `volatile sig_atomic_t` nor lock-free atomics have an unspecified value inside interrupt handlers, and become undefined if you modify them. – zneak May 15 '15 at 15:41
  • I agree with @Zneak here. Lets take a Keil compiler for example, they have halt instructions defined in the notes. It stops the Program counter and wakes up as soon as the interrupt line hits. – SandBag_1996 May 15 '15 at 16:16
  • 4
    Side note: `while(true){}` is not UB in C11; its "may be assumed to terminate" clause excludes loops whose controlling expression is a constant expression. – T.C. May 15 '15 at 16:25
  • Just curious - have you ever seen the loop being removed by any compiler? I've done quite some embedded programming for small front-end CPUs and used exactly the `while(1);` construct. I've never seen problems with it. – Support Ukraine May 15 '15 at 19:30
  • What does the standard say about `here: goto here;` ? – wcochran May 15 '15 at 21:05
  • @wcochran: The same above linked rules apply. – Lightness Races in Orbit May 16 '15 at 03:54
  • @StillLearning: It's the _unexpected_ issues that I'm concerned about. By definition, we cannot enumerate them. Only the expected ones we can workaround in advance. In that spirit, I choose to leave no margin for error. – Lightness Races in Orbit May 16 '15 at 03:55
  • @LightnessRacesinOrbit - I see. In that case I guess the second part of your question is also the answer, i.e. `volatile int dummy=1; while(dummy);` This question also discuss the topic a bit: http://stackoverflow.com/questions/4437527/ – Support Ukraine May 16 '15 at 04:39
  • 1
    [There is currently interest in how LLVM will handle this issue](http://lists.cs.uiuc.edu/pipermail/llvmdev/2015-July/088126.html). How compiler makers think about it could give some insight into what's allowable and what isn't. – zneak Jul 16 '15 at 18:55

1 Answers1

2

How about looking at the assembly language output from your compiler?

g++ -std=c++0x x.cpp -S

outputs:

.L2:
        jmp     .L2

and

clang++-3.5 -S -std=c++11 x.cpp

outputs:

.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB0_1
G. Allen Morris III
  • 1,012
  • 18
  • 30