1

Suppose I have the following C code:

/* clock.c */

#include "clock.h"

static volatile uint32_t clock_ticks;

uint32_t get_clock_ticks(void)
{
    return clock_ticks;
}

void clock_tick(void)
{
    clock_ticks++;
}

Now I am calling clock_tick (i.e.: incrementing clock_ticks variable) within an interruption, while calling get_clock_ticks() from the main() function (i.e.: outside the interruption).

My understanding is that clock_ticks should be declared as volatile as otherwise the compiler could optimize its access and make main() think the value has not changed (while it actually changed from the interruption).

I wonder if using the get_clock_ticks(void) function there, instead of accessing the variable directly form main() (i.e.: not declaring it as static) can actually force the compiler to load the variable from memory even if it was not declared as volatile.

I wonder this as someone told me this could be happening. Is it true? Under which conditions? Should I always use volatile anyway no matters if I use a "getter" function?

Peque
  • 13,638
  • 11
  • 69
  • 105
  • the `volatile` in your case is essential. It is making the difference when the variable is accessed from the interrupt. This way the compiler knows that there is something is accessing and modifying the variable which is not known to it. Otherwise your "getter" will (might) return a constant, initial value of the variable. – Eugene Sh. Oct 10 '17 at 21:55
  • https://stackoverflow.com/questions/5822386/the-volatile-keyword-in-c-language – Krassi Em Oct 10 '17 at 21:59
  • The getter function might get inlined. There might be cache coherency issues that might exhibit themselves as non-deterministic behavior. – jxh Oct 10 '17 at 21:59
  • @EugeneSh. Thanks for your comment! Would you mind adding an answer explaining why the getter does not make any difference? – Peque Oct 10 '17 at 22:03
  • @jxh Thanks for your comment! Would you mind adding an answer explaining why the getter does not make any difference? – Peque Oct 10 '17 at 22:03
  • Even with the `volatile`, `clock_ticks++;` is not an atomic operation. There's no guarantee in C that you'll see a coherent value. Your *platform* may give you that guarantee, but the language doesn't. For example, a 16-bit platform might not even give atomic *read* access to a `uint32_t`. – Andrew Henle Oct 10 '17 at 22:06
  • @AndrewHenle Thanks for the hint. Yeah, the platform is a 32-bit platform. – Peque Oct 10 '17 at 22:11

2 Answers2

2

A getter function doesn't help in any way here over using volatile.

  • Assume the compiler sees you've just fetched the value two lines above and not changed it since then.
  • If it's a good optimizing compiler, I would expect it to see the function call has no side effect simply optimize out the function call.

If get_clock_ticks() would be external (i.e. in a separate module), matters are different (maybe that's what you remember).

Something that can change its value outside normal program flow (e.g. in an ISR), should always be declared volatile.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
tofro
  • 5,640
  • 14
  • 31
  • Thanks for the answer. Will wait until tomorrow before accepting in case someone wants to add something here. ^^ – Peque Oct 10 '17 at 22:20
  • The code shown is from a file `clock.c` and includes a header `clock.h`. Code using the clock functions (e.g. the file containing `main()`) would only include the header and there'd be no opportunity for inlining either function. Of course, there's nothing to stop the OP from including the `clock.c` file, but that isn't what would be expected. (The code also doesn't show how the interrupts are set up, etc.) – Jonathan Leffler Oct 10 '17 at 22:27
  • @JonathanLeffler You assume we've seen all of "clock.c"? I don't necessarily do. There's a difference in optimising options when `get_clock_ticks()` is called from outside or inside "clock.c" (Which makes matters even worse because "doesn't work" mutates to "seems to work sometimes") – tofro Oct 10 '17 at 22:29
  • Actually, I point out that we probably haven't seen all of `clock.c`. What I'm getting at is that `clock.c` is probably a source file for a set of library functions, and the consumers of the the functionality provided are in other source files altogether, and those files do not get to see the source for these functions, so there isn't a chance to inline the code. Of course, the OP may be writing their whole program in `clock.c`; I'm suggesting that it is unlikely (not least because there's no point in the `clock.h` header if no other file uses the functions defined in `clock.c`). – Jonathan Leffler Oct 10 '17 at 22:33
  • Agree it's unlikely all of the program is in "clock.c". But neither you nor I can be 100% sure `get_clock_ticks()` is never called from inside "clock.c"? As soon as it is, it's likely to be optimized away. – tofro Oct 10 '17 at 22:35
  • In this particular case `get_clock_ticks()` is not called from inside "clock.c". The main code is outside `clock.c` and there is a `clock.h` with the non-static function headers declared, which is the one included in other files. Anyway I was interested also in this discussion you are having: knowing which cases could be considered (even though I now understand that generally I cannot assume the "getter" function helps at all). :-) – Peque Oct 10 '17 at 22:37
  • In this case, you're safe. But you'd be safer against further modifications of "clock.c" if you'd use `volatile` (especially as it comes at no cost whatsoever) – tofro Oct 10 '17 at 22:40
  • An interesting question would be what link-time optimization is allowed to do here. I must admit I don't know. – tofro Oct 10 '17 at 22:50
  • @tofro: Anything that doesn't change the semantics of the program. – jxh Oct 10 '17 at 22:51
1

Don't forget that even if you currently compile the code declaring get_clock_ticks and the code using it as separate modules, perhaps one day you will use link-time or cross-module optimisation. Keep the "volatile" even though you are using a getter function - it will do no harm to the code generation in this case, and makes the code correct.

One thing you have not mentioned is the bit size of the processor. If it is not capable of reading a 32-bit value in a single operation, then your get_clock_ticks() will sometimes fail as the reads are not atomic.

David
  • 132
  • 3