I have a code snippet as given below
static unsigned int ticks; void IRQ_SomeTimer(void) { ticks ++; } int main() { int sav_ticks; sav_ticks = ticks; while (sav_ticks + TIME_TO_WAIT > *(volatile unsigned int *)&ticks); /* Do somethine here */ return 0; }
This compiles and runs on ARM-Mx targets up to optimization level O2, at optimization level O3 the code generated for
while (sav_ticks + TIME_TO_WAIT > *(volatile unsigned int *)&ticks);
is the same as the code generated for
while (sav_ticks + TIME_TO_WAIT > ticks);
Hence the code generated by O3 never comes out of the while loop!! Is this a BUG in Keil armcc(?) I am using Keil U-Vision (MDK ARM 4.73).
Apologies for making the typo I intended to declare start_ticks as "unsigned int", I usually use uint32_t and just for the sake of clarity I put unsigned int. But that ain't the point and neither is what return from main is gonna do. The point is that will the cast to volatile pointer works as expected or not!?
Below is the code generated by -O2
;;;14 while (sticks + TICKS_TO_WAIT > *(volatile uint32_t *)&ticks); 00000c 6808 LDR r0,[r1,#0] ; ticks 00000e f500707a ADD r0,r0,#0x3e8 |L1.18| 000012 680a LDR r2,[r1,#0] ; ticks 000014 4282 CMP r2,r0 000016 d3fc BCC |L1.18|
As we can see the branch to L1.18 correctly loads the value of ticks before comparing it against the stored ticks value.
Below is the code generated by -O3
;;;14 while (sticks + TICKS_TO_WAIT > *(volatile uint32_t *)&ticks); 00000c 6808 LDR r0,[r1,#0] ; ticks 00000e 6809 LDR r1,[r1,#0] ; ticks 000010 f500707a ADD r0,r0,#0x3e8 |L1.20| 000014 4281 CMP r1,r0 000016 d3fd BCC |L1.20|
In the above assembler listing branch to L1.20 is not loading the ticks back again.
Just for reference, below is the source against which the assembler listing was generated
#include <stdint.h> #define TICKS_TO_WAIT 1000 static uint32_t ticks; void SysTick_Handler(void) { ticks ++; } int main() { uint32_t sticks; sticks = ticks; while (sticks + TICKS_TO_WAIT > *(volatile uint32_t *)&ticks); return 0; } void SystemInit(void) {}
P.S: As I mentioned before the point is not about SysTick not being initialized, or why main is returning or anything other than the cast to volatile pointer.
The variable is modified by an interrupt and your testing it in a loop in main.
Why would you not declare the variable as being volatile? Why do you even try to use the casting?
The program I posted is just a simple example, but on a complex projects there will be many places where the identifier of interest will be referenced. By cast I could selectively make the identifier to behave as volatile and hence obtaining more optimized code than the one where it is just declared volatile, and you could also make access to extern storage class identifier as volatile without modifying the declaration itself, and there are many reasons why people want their code be in a certain way.
Just to get give an idea of how other tools handle this, I have generated the assembler listing for the same source file with various compilers
Here is the IAR generated code (with highest possible optimization supported by the tool)
// 14 while (sticks + TICKS_TO_WAIT > *(volatile uint32_t *)&ticks); ??main_0: LDR R2,[R0, #+0] CMP R2,R1 BCC.N ??main_0
As you could see branch to ??main_0 is reloading the object from memory.
Here is the GCC generated code (with -O3)
65 .L5: 12:main.c **** uint32_t sticks; 13:main.c **** sticks = ticks; 14:main.c **** while (sticks + TICKS_TO_WAIT > *(volatile uint32_t *)&ticks); 66 000c 1368 ldr r3, [r2] 67 000e 8B42 cmp r3, r1 68 0010 FCD3 bcc .L5
Again the branch to .L5 will properly reload the object from memory.
I would expect the same kind of code to be generated by Keil (armcc) too.
By cast I could selectively make the identifier to behave as volatile and hence obtaining more optimized code than the one where it is just declared volatile
Who the heck cast things to volatile pointers, and back-and-forth, project's got to be a nightmare to manage, for nominal to non-existent benefit.
Code it as ((volatile uint32_t)ticks)
Does the example I presented compile cleanly on all the compilers, and optimization settings? It's simpler, cleaner, and doesn't break when the count wraps.
Submit your bug report to your support contact at Keil
"selectively make the identifier to behave as volatile"
Why would you want it so selectively behave as volatile?
Surely, a variable is either volatile, or it isn't?!
"on a complex projects"
In a complex project, the last thing you want is schizophrenic variables which are sometimes volatile, and other times not!
"Preceding an expression by a parenthesized type name converts the value of the expression to the named type. This construction is called a cast [44]".
And the note [44] says,
"A cast does not yield an lvalue. Thus a cast to qualified type has the same effect as a cast to the unqualified version of the type"
volatile is a qualifier - so I think that says that the compiler is correct to ignore the volatile in your cast...?
Well that settles it (Thank you Andrew Neil).
By cast I could selectively make the identifier to behave as volatile
... and by doing so, you're lying to your tools. Let there be no doubt about one thing: that variable is volatile, period. So you're putting each and every piece of your code that you made believe otherwise at a considerable risk of breaking, for no good reason at all. Pardon the French, but that's just utterly foolish.
and hence obtaining more optimized code than the one where it is just declared volatile
Looks like you need to hear the applicable words of wisdom, loud and clear:
Premature optimization is the root of all evil. (Donald E. Knuth)
Well the funny thing is the code below shows error in Keil
uint8_t x; *(const uint8_t *)&x = 0x48;
It says the resulting expression from the Left hand side of the = operator is a non modifiable 'lvalue'. If cast to the qualified type has the same effect as cast to the unqualified type then the above should be perfectly valid!!
Oops I spoke too soon! After a little bit of analysis I think Footnote [44] of the standard (in draft foot note [86]) does not settle it after all!!
So the note says "A cast does not yield an lvalue. Thus, a cast to a qualified type has the same effect as a cast to the unqualified version of the type." Let us look at what this says
int x; long int y; y = (long) x; y = (volatile long) x;
In the above code the type is "long" and the cast converts it to "volatile qualified long", hence the standard says the compiler to consider the cast to "volatile long" as the same as cast to "long".
Well if we consider a type "int *" the volatile qualified type of "int *" is "int * volatile" and not "volatile int *" [this is totally a different type]. So according to the standards the compiler is free consider cast to "int *" and a cast to "int * volatile" as the same, but it is supposed to do the conversion according to standard given at 6.5.4 Cast operators paragraph 3
Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.
In my original example I do a conversion of "uint32_t *" to "volatile uint32_t *" which is not permitted by the constraint specified in 6.5.16.1 [it permits conversion of "volatile uint32_t *" to "uint32_t *" but not vice-versa]. Hence the compiler should consider my explicit cast. It is indeed a bug in armcc.
To shed more light on this as I mentioned before
the above code shown error [perfectly good] as the compiler did the conversion of type-x to type-y [Where type-x is "uint8_t *" and type-y is "const uint8_t *" and type-y is not a qualified type of type-x]
Now considering the code below
uint8_t x; *(uint8_t * const)&x = 0x48;
compiles with a simple warning as given below
main.c(15): warning: #191-D: type qualifier is meaningless on cast type
Which is apt according to the section of standards mentioned by "Andrew Neil"
P.S: Please don't ask me why I am using const and not volatile.