8

I have some example code (from Nordic), which includes next lines:

    NRF_RADIO->EVENTS_END = 0;
    (void)NRF_RADIO->EVENTS_END;

NRF_RADIO->EVENTS_END is an embedded register which is set to 0 at the first line.

Can someone explain what is the meaning of the second line?

Thanks Yaron

alk
  • 69,737
  • 10
  • 105
  • 255
yaron
  • 101
  • 3

2 Answers2

7

The second line "only" reads the register back. It does so, explicitly ignoring the actual value which is read, by prepending the (void) cast to void.

The line has two specialties which probably makes understanding it non-trivial:

  1. reading without using the value, why?
  2. apart from not using the value, also say explicitly that it gets ignored, why?

The reason for 1 is very likely the fact that (according to OPs statement) a peripheral register is involved. This can imply mechanisms which require a seemingly useless read. For example:

  • the chip supplier has simply required, that a dummy read is necessary for reliable, chronologically predictable effect of the preceding write access;
    this is sometimes (at least by me and my colleagues) described as the "recommended sequence", i.e. a not neccessarily intuitive set of statements, proscribed by manufacturer to be observed to ensure functionality
  • the write access has a reliable effect, but in order to ensure the desired chronological order of it, in comparison with other statements, it is neccessary to read back from the register
    • this is usually accompanied by a "volatile" keyword in the declarations which represent the register
    • this possibly is a (separate) "recommended sequence" which is only necessary for this chronology-affected use case
    • note that two consecutive statements two different special registers can have differently "fast" effect, so that the second one takes effect before the first one
    • one example is a first access to a peripheral like a port, which is connected via a slow bus;
      followed by a second access to a faster connected peripheral, e.g. a memory-mapped interrupt controller;
      the need for chronological order can arise from not wanting to reconfigure the interrupt before the port has been reconfigured

The reason for 2 is completely separate and can for example be:

  • the (possible special, hardware specific) compiler gives warnings for all ignored return values;
    many compilers do so, because ignoring return values is usually a potential source of errors or could indicate a fundamental misunderstanding by the programmer
  • on top of the compiler, static analysis tools are used, which have similar goals: detect anything, which by experience might indicate errors and improved maintainability via coding styles

Edited for readability and better structure, without changing the basic logic; in order to hopefully prevent misunderstandings, which I assume the two downvoters had. If there are sound reasons for downvoting, please let me know.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
-1

For some devices, reading a register might have side effects. For example, I/O memory:

I/O memory is simply a region of RAM-like locations that the device makes available to the processor over the bus. This memory can be used for a number of purposes, such as holding video data or Ethernet packets, as well as implementing device registers that behave just like I/O ports (i.e., they have side effects associated with reading and writing them).

The following line is probably meant to activate some kind of side effect on this device:

(void)NRF_RADIO->EVENTS_END;

Sometimes you will just want to read the data back from the register to make the CPU wait for the data to be written.

However just reading the data without using it might make the compiler issue a warning and will eventually remove this statement completely if optimization is used (as shown below)

Solution (edited thanks to the comments below)

  • Adding a cast to void assert the compiler that you know what you are doing and will not issue a warning.
  • To avoid removing this statement from the program, the fields in the struct should also be volatile.

For example

To demonstrate how adding/removing (void)/volatile from your code might affect the produced code, I'll show here a few examples.

Note: The following examples are undefined behavior for C/C++, but are well defined under gcc + x86.

The following program will produce a warning, but running it will not produce segmentation fault because the pointer will never be read (the compiler will consider it redundant).

struct example {
    int b;
};

int main() {
   struct example * a = NULL;
   a->b;
   return 0; 
}

The following program will produce a warning, but running it will produce segmentation fault due to the use of volatile. Although the compiler is aware that this location needs to be used directly, thus it cannot know what side effects it might have.

struct example {
    volatile int b;
};
  
int main() {
   struct example * a = NULL;
   a->b;
   return 0; 
}

The following program will not produce any warning, but running it will produce a segmentation fault.

struct example {
    volatile int b;
};
  
int main() {
   struct example * a = NULL;
   (void)a->b;
   return 0; 
}

The following program will not produce any warning, and running it will not produce a segmentation fault.

struct example {
    int b;
};
    
int main() {
   struct example * a = NULL;
   (void)a->b;
   return 0; 
}
Community
  • 1
  • 1
Liran Funaro
  • 2,750
  • 2
  • 22
  • 33
  • 1
    Is there any source for this statement? `Adding cast to void assert the compiler that you know what you are doing and will not remove this statement from the program.` – Sush May 07 '17 at 09:35
  • 4
    "*A quick search didn't yield anything.*" ...and there probably isn't at all. Casting a value to `void` doesn't necessarily keep the compiler from removing code it feels to do "nothing relevant". All it probably does is silencing the compiler, to not issue a warning complaining "*code has no effect*" or alike. To force the compiler to not optimise away a variable declare it `volatile`, which in the OP's case might be hidden inside the marco-soup served by `NRF_RADIO->EVENTS_END`. – alk May 07 '17 at 09:44
  • Should we conclude that this answer is not correct and should we revoke the upvotes and instead give it downvotes? – Paul Ogilvie May 07 '17 at 09:54
  • Thanks for your comments. I verified this and you are correct. The casting to void will only prevent warnings. – Liran Funaro May 07 '17 at 09:57
  • 1
    Statements like `(void) var;` are the usual way to make at least gcc shut up about unused variables or arguments. A statement with just `var;` without the cast would make it complain about a statement that doesn't do anything. Other compilers may or may not behave similarly. – ilkkachu May 07 '17 at 10:02
  • 1
    This `a->b;` (with `a == NULL`) invokes undefined behaviour in *any* case, if *not* optimised away, on which one *cannot* rely! – alk May 07 '17 at 10:05
  • If there were no segmentation fault, I can say for sure that this was optimized away. I rely on that. I could disassemble the code, but not today. If a segmentation fault happened, then I know that an attempt to read from that address was issued. This verification method might not work all the time, but if it did, then the conclusions are sound. – Liran Funaro May 07 '17 at 10:07
  • Your "examples" are all Undefined Behaviour due to dereferencing a null pointer, it's absolutely worthless "information" to know whether or not you happen to get a segmentation fault on some system at some point in time – M.M May 10 '17 at 19:20
  • It is well defined under gcc+x86 and its meant to demonstrate the different behavior of the compiler when using (void) and volatile. This meant to show how this can have an effect on the produces code when it is important to produce an "unused" for its side affect that the compiler is not aware of. – Liran Funaro May 10 '17 at 22:00