6

Consider a while loop in ANSI C whose only purpose is to delay execution:

unsigned long counter = DELAY_COUNT;
while(counter--);

I've seen this used a lot to enforce delays on embedded systems, where eg. there is no sleep function and timers or interrupts are limited.

My reading of the ANSI C standard is that this can be completely removed by a conforming compiler. It has none of the side effects described in 5.1.2.3:

Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

...and this section also says:

An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

Does this imply that the loop could be optimised out? Even if counter were volatile?

Notes:

  1. That this is not quite the same as Are compilers allowed to eliminate infinite loops?, because that refers to infinite loops, and questions arise about when a program is allowed to terminate at all. In this case, the program will certainly proceed past this line at some point, optimisation or not.
  2. I know what GCC does (removes the loop for -O1 or higher, unless counter is volatile), but I want to know what the standard dictates.
Community
  • 1
  • 1
detly
  • 29,332
  • 18
  • 93
  • 152
  • As long as the *obserable* behaviour is not changed, C standard wouldn't prohibit optimizing out this loop. So the standard dictates nothing specifically. – P.P Jan 21 '13 at 23:39

4 Answers4

12

C standard compliance follows the "as-if" rule, by which the compiler can generate any code that behaves "as if" it was running your actual instructions on the abstract machine. Since not performing any operations has the same observable behaviour "as if" you did perform the loop, it's entirely permissible to not generate code for it.

In other words, the time something takes to compute on a real machine is not part of the "observable" behaviour of your program, it is merely a phenomenon of a particular implementation.

The situation is different for volatile variables, since accessing a volatile counts as an "observable" effect.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
9

Does this imply that the loop could be optimised out?

Yes.

Even if counter were volatile?

No. It would read and write a volatile variable, which has observable behavior, so it must occur.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
2

If the counter is volatile, the compiler cannot legally optimize out the delay loop. Otherwise it can.

Delay loops like this are bad because the time they burn depends on how the compiler generates code for them. Using different optimization options you can achieve different delays, which is hardly what one wants from a delay loop.

For this reason such delay loops should be implemented in assembly language, where the programmer controls the code fully. This typically applies in embedded systems with simple CPUs.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • 2
    Whether you implement this in assembly language or not is irrelevant in a sense. Delay loops that rely on a certain number of iterations are flakey - the "delay" varies based on a number of factors, almost all of which are outside the programmer's control. **Don't use such delay loops.** – Nik Bougalis Jan 21 '13 at 23:37
  • 1
    @NikBougalis Not necessarily true. If the CPU is a relatively dumb one and executes instructions using the same number of clocks every time (because it's dumb and doesn't have caching) and if the clock rate doesn't change, delays are going to be pretty stable, unless the interrupts are enabled and ISRs execute frequently and take a lot of time. And during hardware initialization interrupts are often disabled temporarily or until after the initialization has completed. If all those conditions are met, no problem with delay loops. – Alexey Frunze Jan 21 '13 at 23:41
  • @NikBougalis RISC processors on embedded systems have countable delays most of the time and delay loops can have their time bound pretty well. – Jesus Ramos Jan 21 '13 at 23:43
  • Are we talking about such embedded processors, or the general case, where something is running in userspace on a non-RTOS that implements premptive multitasking? The simple fact is that in modern, pre-emptive multitasking non-realtime operating systems delay loops are, at best, unreliable. – Nik Bougalis Jan 21 '13 at 23:44
  • 3
    @NikBougalis True. But since the OP explicitly mentioned embedded, it's not excluded. – Alexey Frunze Jan 21 '13 at 23:46
  • 1
    FTR, I certainly agree that this is a poor approach, but I'm trying to identify whether there's an even more dangerous issue with them. Incidentally, I've seen mixed results with this technique even in highly controlled embedded systems, with both "dumb" compilers with very limited optimisation, and with smarter compilers. Sometimes the problem is as @NikBougalis describes - the loop is there, but the delay is not at all linear with `DELAY_COUNT` and can change with surrounding code. Sometimes it's the opposite: the delay is linear, but the smallest unit is much, much longer than you'd expect. – detly Jan 22 '13 at 00:04
  • 1
    (I wouldn't even try it on a proper multitasking OS - even `sleep` is a bit hacky when you should be using eg. `select` or mutexes.) – detly Jan 22 '13 at 00:04
1

The standard dictates the behavior you see. If you create a dependency tree for DELAY_COUNT you see that it has a modify without use property which means it can be eliminated. This is in reference to the non volatile case. IN the volatile case the compiler cannot use the dependency tree to attempt to remove this variable and as such the delay remains (since volatile means that hardware can change the memory mapped value OR in some cases means "I really need this don't throw it away") In the case you're looking at if labeled volatile it tells the compiler, please don't throw this away it's here for a reason.

Jesus Ramos
  • 22,940
  • 10
  • 58
  • 88