Hallo zusammen,
wie verhält sich GCC bei eingeschalteter Optimierung wenn eine volatile
Variable mehrfach hintereinander gelesen wird. Also beispielsweise:
1
#define REG (*((volatile unsigned int*)0x1234))
2
3
unsigned int temp;
4
5
temp = REG;
6
temp = REG;
Ist hierbei sichergestellt, dass das Register auch mehrfach gelesen wird
?
Der Compiler könnte z.B. ja denken, dass das erste Lesen unnötig ist, da
im zweiten Schritt der gelesene Wert überschrieben wird...
klaus schrieb:
> Der Compiler könnte z.B. ja denken, dass das erste Lesen unnötig ist, da> im zweiten Schritt der gelesene Wert überschrieben wird...
Genau das ist der Zweck von 'volatile'.
Den Compiler daran zu hindern, genau das zu denken.
volatile sorgt dafür, dass jeder Zugriff auch exakt so ausgeführt wird,
wie du ihn hingeschrieben hast.
Gilt dies auch für das Lesen eines Registers wenn der gelesene Wert nie
verwendet wird?
Aus der Definition die ich gefunden habe geht das nicht explizit hervor:
"Das Schlüsselwort volatile teilt dem Compiler mit, daß die mit name
bezeichnete Variable mit dem Datentyp typ durch Ereignisse außerhalb der
Kontrolle des Programms verändert werden kann."
klaus schrieb:
> Gilt dies auch für das Lesen eines Registers wenn der gelesene Wert nie> verwendet wird?
Jeder Zugriff muss so gemacht werden, wie du ihn hingeschrieben hast!
Ausnahmslos jeder! Egal wie sinnlos er für den Compiler und seine
Datenflussanalyse auch aussehen mag.
volatile bedeutet für den Compiler: No optimizations allowed!
klaus schrieb:
> Gilt dies auch für das Lesen eines Registers wenn der gelesene Wert nie> verwendet wird?
Es wäre übel, wenn die Anweisung dann entfallen würde.
Klassischer Anwendungsfall z.B. UDR muss beim UART-Empfang auf dem AVR
ausgelesen werden. Nach dem Auslesen kann man es wegschmeissen (nicht
weiterverwenden), aber Lesen muss man es, weil ansonsten z.B. der
Empfangsinterrupt immer wieder käme.
Ein
{
...
{
unsigned int dummy = UDR;
}
...
}
muss in Programmanweisung(en) übersetzt werden, selbst wenn das lokale
dummy nie verwendet wird.
Wäre auch meine eigene Einschätzung gewesen. Bei der LPC2000 Serie hatte
ich mit ähnlichen Registern zu tun. Habe folgendes gefunden, da stehts
explizit:
"Objects declared as volatile are not used in certain optimizations
because their values can change at any time. The system always reads the
current value of a volatile object at the point it is requested, even if
a previous instruction asked for a value from the same object. Also, the
value of the object is written immediately on assignment."
Danke für eure Antworten!
REG ist volatile, muß also beide Male gelesen werden.
temp ist aber nicht volatile.
So wie ich das sehe, könnte der Optimierer daraus zwei x Lesen und nur 1
x Schreiben machen.
>> könnte da sogar reichen...
Ich fall vom Stuhl, das tuts!
Ist das auch wirklich im C-Standard garantiert, daß dann ein Lesezugriff
erfolgen muß?
Peter
Sicher. Das ist eine Expression wie jede andere auch.
3 + 4;
ist Standard-C.
Dies geht deshalb, weil in C ja auch eine Zuweisung eine Expression ist,
die ein Ergebnis liefert. So gesehen besteht in C für den Compiler kein
Unterschied zwischen
a;
und
a = 5;
beides sind Ausdrücke, die einen Wert liefern. Nur hat der 2te Ausdruck
noch den Nebeneffekt, dass während seiner Auswertung a noch einen neuen
Wert bekommt :-)
Wenn hier schon die GCC volatile Spezialisten zusammen sind, habe ich
auch noch eine Frage dazu:
volatile int16_t variable
definiert eine Variable deren Inhalt als Integer mit Vorzeichen bewertet
wird. Sodann übergebe ich diese Variable als Parameter per Reference in
eine Funktion:
funktion (&variable);
Dortselbst schreibt diese irgendwas rein:
void funktion(int16_t* variable)
{ *variable = irgendwas;
if ((int16_t) *variable - signedintegervariable) nochwas();
}
Nun kommt GCC mit einer unverständlichen Warnung von wegen "passing
argument of funktion discards qualifiers from pointer target type" oder
so ähnlich. Tatsächlich scheint GCC den Typ plötzlich komplett zu
ignorieren und zwar inklusiv seiner signed Eigenschaft. Lässt man
volatile weg, kommt keine Warnung und alles ist richtig.
Der Kampf geht aber weiter: Lässt man in der if Zeile den Typecast weg,
so ist das Resultat (mit und ohne volatile) von signed int minus signed
int nicht etwas ein signed int sondern ein unsigned int. Mit anderen
Worten: Das if funktioniert nur mit typecast aber nicht ohne. Gleicher
Effekt mit und ohne eigeschalteten Optimierer
J. V. schrieb:
> Nun kommt GCC mit einer unverständlichen Warnung von wegen "passing> argument of funktion discards qualifiers from pointer target type" oder> so ähnlich.
Womit er recht hat, denn ein normaler Zeiger darf nicht auf eine
"volatile" Variable zeigen, weil ebendiese besondere Eigenschaft dadurch
verloren ginge. Andersrum ist jedoch zulässig.
Also: void funktion(volatile int16_t* variable)
Den Rest der Frage habe ich nicht verstanden, denn das ob das Ergebnis
einer Subtraktion 0 ist hängt heutzutage nicht von der
Vorzeicheneigenschaft der Rechnung ab.
@ Karl heinz Buchegger (kbuchegg) (Moderator)
>Sicher. Das ist eine Expression wie jede andere auch.
Eine was????
Du meinst einen logischen AUSDRUCK!!!
Nieder mit dem DENGLISCH!!!
MFG
Falk
Tatsächlich funktioniert es korrekt, wenn man volatile an beiden Stellen
hinschreibt.
Im zweiten Teil soll es korrekt heissen:
if ((int16_t) (*variable - signedintegervariable) > 0 ) nochwas();
also wenn die Zahl positiv ist. Hier mal der Originalcode daß ich nicht
nochwas vergesse, wo ich mir für den Systick (von meinem STM32
Lieblingsteil) einen Timer gebastelt habe weil ich es nicht sehen kann,
daß STM es in den Beispielen immer so macht wie man es nicht sollte.
(nämlich in einer Loop die Zeit zu verbraten)
1
voidSetFlop(int16_t*timer,int16_tlength)
2
{
3
*timer=(tictac+length);
4
}
5
6
intTestFlop(int16_t*timer)
7
{
8
if((int16_t)(tictac-*timer)>0)return(TRUE);
9
elsereturn(FALSE);
10
}
Die Nutzung sieht dann so aus:
1
voidmz0(void)
2
{SetFlop(&MaTimer,2000);// Timer starten
3
MaZust=1;
4
}
5
6
voidmz1(void)
7
{if(TestFlop(&MaTimer))MaZust=2;}
8
9
voidmz2(void)
10
{...usw
11
12
voidStateMachine()
13
{switch(MaZust)// Berechneter Sprung mit Zustandsmerker
14
{case0:mz0();break;
15
case1:mz1();break;
16
case2:mz2();break;//usw.
17
}
18
}
Mit dem Typecast funzt alles Wunderbar, ohne Typecast aber nicht im
negativen Bereich des tictac Systick. tictac ist selbstverständlich
wegen Schreibvorgang im Systick Interrupt auch volatile int16_t. Scheint
dann aber kein volatile Problem zu sein sondern ich hab was anderes
übersehen?
der Fehler lag bei
extern volatile int32_t tictac;
und mit
extern volatile int16_t tictac;
ist alles gleichzeitig richtig und es funzt prima auch ohne typecast.
Also nichts mit volatile zu tun wie ich wegen der Warnung vermutete.
Manchmal hilft es schon, wenn man nur irgenjemand was vorjammern kann.
Dass auf einer 32-Bit Maschine und
int16_t a, b;
zwischen
(a - b) [1]
und
(int16_t)(a - b) [2]
ein Unterschied besteht, das sollte hoffentlich klar sein. Nämlich
beispielsweise bei a=30000 und b=-20000. Denn dann ist [1] 50000, und
[2] -15536.
Neben dem Jammern hätte auch die Information geholfen, dass von einer 32
Bit-Maschine die Rede ist. Das ging aus dem ursprünglichen Posting nicht
hervor.
> das sollte hoffentlich klar sein.
ääh warum das nun ?
int16_t minus int16_t sollte doch doch unabhängig von einem 32 bit
System immer in16_t geben?
Wenn ich natürlich int32_t minus int16_t rechne kommt int32_t raus wo
das Vorzeichenbit dann etwas weiter links liegt was mit dem
unfreiwilligen und unnötigen Typecast wieder zurückgebogen wurde
J. V. schrieb:
> int16_t minus int16_t sollte doch doch unabhängig von einem 32 bit> System immer in16_t geben?
Keineswegs. Ein Blick in ein C Handbuch sollte dir das verdeutlichen.
Auf Neudeutsch unter "integer promotion rules" zu finden.
Kurzfassung: Alles kleiner als "int" wird zu "int" erweitert und erst
dann wird gerechnet.
> Auf Neudeutsch unter "integer promotion rules" zu finden.
hm - war mir bislang nicht wirklich klar da signed normal nie gebraucht
und unsigned wohl der Default Fall bei GCC und auch anderen Compilern
scheint.
In meinem C Büchlein heist es tatsächlich (im noch nie gelesenen)
Anhang:
Pointer Arithmetik und Vergleiche sind im allgemeinen nicht portabel.
Verwendet man eine cast Konstruktion, ist die Portabilität
gewährleistet. Pointer Vergleiche werden auf einigen Systemen als
signed, auf anderen als unsigned Vergleiche vorgenommen.
Zu was schreibt man den Typ dann noch hin wenn der Compiler doch macht
was er will?
Krishna schrieb:
>>könnte da sogar reichen...> wäre aber was für einen obscated c contest
Den gibt es aber eh nicht mehr (den International Obfuscated C Code
Contest), oder? Ich meine sie haben es bis heute nicht geschafft, die
Sourcecodes von 2006 zu veröffentlichen. Geschweige denn dass es eine
neue Runde gäbe.
J. V. schrieb:
> hm - war mir bislang nicht wirklich klar da signed normal nie gebraucht> und unsigned wohl der Default Fall bei GCC und auch anderen Compilern> scheint.
Unsigned ist nie der implizite Normalfall, ausser auf manchen
Maschinen als "char".
> Pointer Arithmetik und Vergleiche sind im allgemeinen nicht portabel.> Verwendet man eine cast Konstruktion, ist die Portabilität> gewährleistet. Pointer Vergleiche werden auf einigen Systemen als> signed, auf anderen als unsigned Vergleiche vorgenommen.
Halte ich in dieser Formulierung für Quark, es sei denn es werden damit
Compiler eingeschlossen, die es mit dem Standard nicht allzu genau
nehmen. Ausserdem sind die erwähten Vergleiche garantiert nur Vergleiche
von Pointern, nicht von Integern, also als "Pointer-Arithmetik und
Pointer-Vergleiche" zu lesen.
Der C Standard definiert, dass die Differenz zweier Adressen vom
gleichen Objekt (also meist Array) als ptrdiff_t ausgedrückt werden
kann, und dass (logischerweise) dieser Typ ein Vorzeichen hat. Es bleibt
aber offen, ob dieser Typ int oder long entspricht.
Plattformen mit ptrdiff_t=long sind beispielsweise 64-Bit PCs, die aus
historischen Gründen trotzdem mit 32-Bit int aber 64-Bit Adressen
arbeiten. Da wird es etwas haarig.
Wenn auf einer 16-Bit Maschine wie üblich ptrdiff_t=int gilt, dann kann
kein Array-Objekt grösser werden als 32767 Bytes, weil sonst diese
Bedingung verletzt wäre (trivial für char-Arrays, gilt das jedoch auch
für andere Arrays).
Ob ein Vergleich zweier vergleichbarer (*) Pointer mit oder ohne
Vorzeichen stattfindet ist für Portabilität belanglos und tatsächlich
von der Maschine abhängig, wenngleich vorzeichenbehaftete Adressräume
selten sind (mir sind nur die Transputer bekannt). Entscheidend ist
jedoch, dass eine berechnete Differenz vorzeichenbehaftet sein muss.
*: Nicht portabel weil völlig undefiniert sind allerdings Vergleiche von
Adressen verschiedener Objekte.
J. V. schrieb:
> Zu was schreibt man den Typ dann noch hin wenn der Compiler doch macht> was er will?
Das Verhalten ist ziemlich gut definiert und der Compiler tut das was er
soll. Vorsicht mit solchen Vorwürfen, wenn man erkennbar grosse Lücken
im Verständnis von C hat.
Von dem Tipp mit eifrigem Einsatz von Casts kann ich nur dringendst
abraten. Ein Cast ist eine Brechstange, die vieles von dem aushebelt,
was der Compiler an Überprüfungen durchführt und sollte so selten wie
möglich eingesetzt werden. Und wenn, dann sollte man genau wissen was
man tut, nicht raten.
Vielleicht ist das Buch 1980 oder so; die Formulierung
ist so wie sie dasteht wirklich Quark.
Wenn Pointerarithmetik per se unbrauchbar wäre, müsste ich meine
ganzen letzten Jahre nochmal neu machen.
>Vielleicht ist das Buch 1980 oder so; die Formulierung>ist so wie sie dasteht wirklich Quark.
es ist von 1987 (1. Auflage Sybex) und dort habe ich es wieder in den
Schrank gestellt weil C auf einem 8085 unbrauchbar war. Vielleicht
sollte ich mir doch mal ein neues Büchlein kaufen denn C89 und C99 und
so sind da dran auch vorbeigegangen.
>Von dem Tipp mit eifrigem Einsatz von Casts kann ich nur dringendst>abraten. Ein Cast ist eine Brechstange, die vieles von dem aushebelt,
Wenn ich es nun korrekt verstanden habe, ist an dieser Stelle aber auch
dann ein Cast zu empfehlen obwohl es auch ohne funktioniert weil nicht
garantiert ist, daß ein nicht explizit einer typbehafteten Variablen
zugewiesenes Resultat nicht gleich dem der beiden Operanden sein muß.
J. V. schrieb:
> Wenn ich es nun korrekt verstanden habe, ist an dieser Stelle aber auch> dann ein Cast zu empfehlen obwohl es auch ohne funktioniert weil nicht> garantiert ist, daß ein nicht explizit einer typbehafteten Variablen> zugewiesenes Resultat nicht gleich dem der beiden Operanden sein muß.
Könntest du diesen Satz mal in ein Beispiel giessen? Der Code oben
eignet sich eher weniger, weil 16-Bit Timer nicht wirklich oft mit
Vorzeichen interpretiert werden und weil das Verhalten von Überlauf bei
vorzeichenbehafteter Rechnung nicht definiert ist.
J. V. schrieb:
> es ist von 1987 (1. Auflage Sybex) und dort habe ich es wieder in den> Schrank gestellt weil C auf einem 8085 unbrauchbar war.
Das erklärt die Aussage. Die erwähnten integer promotion rules gibt es
in ihrer heutigen Form erst seit ANSI-C, aka C89.
Davor, also in K&R-C, war nicht klar definiert, ob beispielsweise die
Differenz zwischen 16-bit signed und 8-bit unsigned nun signed oder
unsigned ist. Der eine Compiler war value-preserving wie C89, der andere
unsigned-preserving und mindestens einer konnte vorsorglich beides.
Insofern konnte es damals tatsächlich vorkommen, dass die Differenz
(int)10 - (unsigned char)20
bei einem Compiler negativ war, beim anderen positiv.
Aber das Thema ist seit C89 durch.
Gast schrieb:
> Dass es immer noch Leute gibt, die sagen dass 'volatile' bedeutet, dass> keine Optimierungen auf die Variable angewendet werden darf!
Was willst du damit sagen? Vor allem: wem?
Gast schrieb:
> Dass es immer noch Leute gibt, die sagen dass 'volatile' bedeutet, dass> keine Optimierungen auf die Variable angewendet werden darf!
Ja, ist echt unfassbar, dass es heutzutage noch Leute gibt, die die
Wahrheit erzählen.
>Könntest du diesen Satz mal in ein Beispiel giessen? Der Code oben>eignet sich eher weniger, weil 16-Bit Timer nicht wirklich oft mit>Vorzeichen interpretiert werden und weil das Verhalten von Überlauf bei>vorzeichenbehafteter Rechnung nicht definiert ist.
Das obenstehende Beispiel zeigt den Cast in der Testflop Funktion.
Ein anderes Beispiel habe ich moentan nicht.
Die vozeichenbehafteten (Software-Timer) gibt es schon immer und ich
glaube, alle Echtzeit Betriebssysteme machen das auch so. Früher halt in
Assembler wo es dazu einen Half-Carry bzw. Rotieren ins Carry gab. Das
Einzige was im Beispiel noch fehlt ist die ISR für den SYSTICK und der
besteht aus tictac++; Falls es noch unklar ist wie es geht:
SetFlop startet eine Zeit
TestFlop prüft ob die Zeit abgelaufen ist
Das ist schon alles und verblüffend einfach. Der immense Vorteil
gegenüber allen anderen Methoden ist, daß auch ohne Multitask beliebig
viele Zeiten (nur begrenzt durch das RAM) gleichzeitig laufen können.
Und zwar ohne daß hierbei Rechenzeit mit dekrementieren oder
inkrementieren von ebenso vielen Zeiten verbraten wird. Einzige
Einschränkung: Bei einem 16 bit Timer ist die längste Zeit 15 bit und
bei einem 32 bit Timer ist die längste Zeit 31 bit usw.
Zustandsmaschinen können dabei ebensoviele gleichzeitig laufen wie
Timer. Die maximale Zykluszeit addiert sich hier als Unschärfe oder
Jitter zur Timerzeit. Also eher ein Aufbau für "sichtbare" längere
Zeiten, so ab 10-100 mSec aufwärts.
>und weil das Verhalten von Überlauf bei>vorzeichenbehafteter Rechnung nicht definiert ist.
Das ist sehr wohl definiert und nachzulesen beim Zweierkomplement.
Möglicherweise gibt es irgendwelche DSPs mit Sättigungsarithmetik aber
das ist dann auch separat dokumentiert und beim STM32 definitiv nicht
so.
Deshalb reicht das Abfragen eines abgelaufenen Timers spätestens nach
einer maximalen Timer Zeit vorzunehmen, um den Überlauf nicht zu
verpassen. Wenn die Zykluszeit hierzu nicht taugt, hat man sowieso was
anderes falsch gemacht. Tut beliebig oft, rund um die Uhr, ohne
volatile, Multitask und in allen Bitbreiten. Diese habe ich
zwischenzeitlich entsprechend dem Zielsystem auf mir bislang unhandlich
erscheinende 32 bit Zahlen umgestellt.
J. V. schrieb:
> Das ist sehr wohl definiert und nachzulesen beim Zweierkomplement.
Nur dass C keine Zweierkomplement-Arithmetik impliziert und
Sättigungs-Arithmetik oder Overflow-Traps durchaus zulässig sind. Nur
bei vorzeichenloser Arithmetik ist in C das Modulo-Verhalten definiert.
> Möglicherweise gibt es irgendwelche DSPs mit Sättigungsarithmetik aber> das ist dann auch separat dokumentiert und beim STM32 definitiv nicht> so.
Ich unterscheide zwischem Implementierung und Definition. Also: Das
Verhalen ist nicht definiert, aber im Fall der STM32-Implementierung
ziemlich bekannt.
Aber zur vorsorglichen Abschreckung ein Beispiel, wie man sich auch in
solchem Kontext verrechnen kann: Wenn eine Maschine nur in halber Breite
multiplizieren kann und eine Multiplikation relativ teuer ist, dann muss
der Code nur 2 der maximal 3 Teilprodukte rechnen, denn wenn beide
mittleren Teilprodukte relevant sind, dann gibt es Überlauf und das
Ergebnis ist formal undefiniert. Es kann also sein, dass eine so
optimierte Multiplikation keine Modulo-Rechnung mehr ist.
Ähnliche Überraschungen sind auch über den Optimizer denkbar. Ein von
der Erwartung abweichendes Verhalten, dass nur bei Überlauf eintritt,
muss er nicht einkalkulieren.
J. V. schrieb:
> SetFlop startet eine Zeit> TestFlop prüft ob die Zeit abgelaufen ist
In diesem Zusammenhang ist ein Cast durchaus sinnvoll. Wobei sich mir
aber die Frage stellt, ob es wirklich sinnvoll ist, solche Timer
unterhalb von "int" anzusiedeln. Und ab "int" tritt das Problem nicht
mehr auf.
Bei RISCs darf man davon ausgehen, dass kleinere Daten zwar im RAM Platz
sparen, aber tendentiell zusätzlichen Code kosten. Bei lokalen Daten und
Parametern (also Registern) ist dieser Effekt besonders stark
ausgeprägt.
>Nur bei vorzeichenloser Arithmetik ist in C das Modulo-Verhalten definiert.
Auch noch das! Müsste aber genauso gehen, warum auch komplizierter?
Werds bei Gelegenheit auf uint32_t umstellen und mit einer Maske auf bit
31 so tun als ob ich das Vorzeichen abfrage. Womit sich die bisherige
Tradition wieder bestätigt hat, von signed möglichst nicht nur bei char
die Finger wegzulassen.
Nicht gleich die Flinte ins Korn werfen, deine Variante funktioniert de
fakto durchaus. Wobei ich allerdings die Timer als "unsigned" laufen
lassen würde, um erst die Differenz nach "int" zu casten. In bekannter
Umgebung brennt da aber in beiden Varianten nichts an.