Forum: Compiler & IDEs volatile cast: GCC Bug oder gültige Optimierung


von Thomas (Gast)


Lesenswert?

Ich habe hier ein kleines Problem, für welches ich zwar einen Workaround 
habe, aber nicht sicher bin, ob das ein GCC Bug ist.

Und zwar sollen im Code Zeichen asynchron über den UART gelesen werden 
bis ein gültiges Längenbyte (!=0) kommt. Über den Code Stil kann man 
streiten, aber er sollte so funktionieren, und tat das auch bis wir Link 
Time Optimization verwendeten und dabei ein Call durch Inlinen ersetzt 
wurde.

Ich hab als Beispiel was gebastelt, das diese Auffälligkeiten zeigt, 
dabei sind 3 Beispiele zu sehen, im ersten wird bei der if Überprüfung 
in der Schleife der zweite Teil weggelassen, obwohl auf (volatile ...) 
gecastet. Darf der Compiler das, obwohl ich ihm sage die Variable kann 
sich (durch die UART ISR) ändern?
Bei den beiden anderen Varianten wird alles wie gewünscht ausgeführt. Am 
besten man kompiliert das immer nur mit einer Schleife, dann sieht man 
im Listing was ich meine. Optimierung ist -Os mit GCC 4.7.2 von 
Launchpad

Plattform ist ein STM32
1
#include <stdint.h>
2
 
3
static char m_cRecBuffer[23];
4
5
void __attribute__((noinline)) uart_read_async(uint8_t* pa_pcBuffer, int pa_nLen)
6
{
7
   volatile uint8_t * pUartBusy = (volatile uint8_t*)0x42; 
8
   *pUartBusy = 1;
9
}
10
 
11
 uint8_t __attribute__((noinline)) uart_is_busy()
12
 {
13
    volatile uint8_t * pUartBusy = (volatile uint8_t*)0x42; 
14
   return *pUartBusy; 
15
 }
16
 
17
 void _exit(int pa_Exit)
18
 {
19
 }
20
 
21
int main(void)
22
{
23
24
   m_cRecBuffer[0] = 0;
25
   uart_read_async(&m_cRecBuffer[0], 1);
26
   
27
   // some pointer to uart driver status, changed in uart ISR
28
   volatile uint8_t * p_UartBusy = (volatile uint8_t*)0x42; 
29
   
30
   // not working
31
   /*
32
   * second condition in if is ignored (0 == (volatile uint8_t)m_cRecBuffer[0])
33
   */
34
   while ((0 == (volatile uint8_t)m_cRecBuffer[0])) {
35
    if (*p_UartBusy != 0 && (0 == (volatile uint8_t)m_cRecBuffer[0])) {
36
      uart_read_async(&m_cRecBuffer[0], 1);
37
    }
38
   }
39
   
40
   // working
41
   while ((0 == (volatile uint8_t)m_cRecBuffer[0])) {
42
    if (uart_is_busy() != 0 && (0 == (volatile uint8_t)m_cRecBuffer[0])) {
43
      uart_read_async(&m_cRecBuffer[0], 1);
44
    }
45
   }
46
   
47
   // working
48
   volatile uint8_t * pcLen = (volatile uint8_t*) &m_cRecBuffer[0];
49
   while (0 == *pcLen) {
50
    if (*p_UartBusy != 0 && 0 == *pcLen) {
51
      uart_read_async(&m_cRecBuffer[0], 1);
52
    }
53
   }
54
}

von Nicolas S. (Gast)


Lesenswert?

Also einen Cast in volatile habe ich noch nie gesehen....

....aber irgendwie fehlen mir hier auch die Deklarationen.

von Max (Gast)


Lesenswert?

Das der
>> (0 == (volatile uint8_t)m_cRecBuffer[0])
Teil wegoptimiert wird, könnte vielleicht daran liegen, dass du
m_cRecBuffer nicht als volatile deklariert ist. Das könntest mal 
ausprobieren.
Aber ich weiß auch nich, was ein volatile cast letztlich für 
Auswirkungen hat.

von Peter D. (peda)


Lesenswert?

Also beim AVR-GCC läßt sich volatile nicht direkt casten.
Nur über den Umweg als Pointer:
1
// force access of interrupt variables
2
#define IVAR(x)         (*(volatile typeof(x)*)&(x))
3
4
uint8_t blub;
5
6
int main()
7
{
8
  if( IVAR(blub) == 0 ){
9
10
  }
11
}

von (prx) A. K. (prx)


Lesenswert?

Wenn man auf eine Variable, die nicht als "volatile" deklariert ist, 
erst einmal zugegriffen hat, dann ist es ziemlich egal, ob man den so 
erhaltenen Wert nachträglich zu "volatile" castet. Das ist so, als ob 
man den Stall zusperrt, nachdem das Pferd schon ausgebüxt ist.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Anmerkung 92 in Abschnitt “6.5.4 Cast operators” im C-Standard
lautet:
1
92) A cast does not yield an lvalue. Thus, a cast to a qualified type has
2
    the same effect as a cast to the unqualified version of the type.

Anders ausgedrückt: das “volatile” im Cast ist an dieser Stelle
wirkungslos.

(Edit: A. K. war schneller.)

von Kindergärtner (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Also beim AVR-GCC läßt sich volatile nicht direkt casten.
Genau das ist das Problem. Du liest die *nicht*-volatile Variable 
m_recBuffer aus (kann also wegoptimiert werden) und castest das 
Ergebnis nach "volatile uint8_t".
In C wäre es so richtig:
1
while (0 == ((volatile uint8_t*)m_cRecBuffer)[0]) {
In C++:
1
while (0 == const_cast<volatile uint8_t*>(&m_cRecBuffer)[0]) {
(const_cast ist auch für volatile zuständig...)
Natürlich wäre es geschickter, m_cRecBuffer gleich komplett als 
"volatile" zu definieren.

Peter Dannegger schrieb:
> #define IVAR(x)         (*(volatile typeof(x)*)&(x))
Und für die 2 anderen C++-Programmierer hier um Forum:
1
template <typename T>
2
inline volatile T& IVAR (T& var) {
3
  return const_cast<volatile T&>(var);
4
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kindergärtner schrieb:
> In C wäre es so richtig:while (0 == ((volatile
> uint8_t*)m_cRecBuffer)[0]) {

Nein, da fehlt ein Adressoperator und eine Dereferenzierung.  Man
kann aber stattdessen auch gleich das Array selbst nehmen:
1
while (*(volatile uint8_t *)m_cRecBuffer == 0) { ... }

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:
>> In C wäre es so richtig:while (0 == ((volatile
>> uint8_t*)m_cRecBuffer)[0]) {
>
> Nein, da fehlt ein Adressoperator und eine Dereferenzierung.

Genauer hinsehen. ;-)

  ((volatile uint8_t*)m_cRecBuffer)[0]
= impliziter Adressoperator:
  (volatile uint8_t*)m_cRecBuffer
+ Dereferenzierung:
  (...)[0]

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> + Dereferenzierung:

Stimmt, die Klammerung war mir entgangen (bzw. das Auge hatte sie
falsch herum sortiert).

von Thomas (Gast)


Lesenswert?

Vielen Dank für die ausführlichen Antworten, wieder was gelernt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.