Insbesondere die letzten beiden Zeilen müssen in dieser Reihenfolge
geschehen. Andernfalls könnte das transmit-Flag schon gesetzt werden,
bevor der Puffer beschrieben wurde. Und natürlich würde der Interrupt
genau dazwischen eintreten...
Beide Variablen sind als volatile gekennzeichnet. Aber bewirkt das
(standardkonform), dass die beiden Zuweiseungen nicht umsortiert
werden...?
Vielen Dank und Grüße,
Sven
Sven P. schrieb:> Beide Variablen sind als volatile gekennzeichnet. Aber bewirkt das> (standardkonform), dass die beiden Zuweiseungen nicht umsortiert> werden...?
ja
Eine weitere Möglichkeit auf Nummer Sicher zu gehen wäre, aus den zwei
Anweisungen eine zu machen, und mit einem Sequence-Point darin, der die
Reihenfolge garantiert:
1
buffer='X',transmit=1;
IMO ist das so sogar besser lesbar, weil es die direkte
Zusammengehörigkeit und Reihenfolgeabhängigkeit der beiden Zuweisungen
dokumentiert.
Peter II schrieb:> Sven P. schrieb:>> Beide Variablen sind als volatile gekennzeichnet. Aber bewirkt das>> (standardkonform), dass die beiden Zuweiseungen nicht umsortiert>> werden...?>> ja
Und könntest du das auch bitte irgendwie, am besten anhand des
C-Standards, belegen...? Das wäre super.
Stefan Ernst schrieb:> Eine weitere Möglichkeit auf Nummer Sicher zu gehen wäre, aus den zwei> Anweisungen eine zu machen, und mit einem Sequence-Point darin, der die> Reihenfolge garantiert:>
1
buffer='X',transmit=1;
IMO ist das so sogar besser lesbar,
> weil es die direkte Zusammengehörigkeit und Reihenfolgeabhängigkeit der> beiden Zuweisungen dokumentiert.
Naja, ob das lesbarer ist...
Schaden tut es nicht, aber der Sequence-Point ist auch schon vorhanden,
wenn man es auf zwei Anweisungen verteilt.
Wie gesagt, ich spekuliere nun schon einige Stunden darüber herum und
weiß trotzdem nicht so recht, wie ich die Spezifikationen im C-Standard
auslegen soll. Drumherumfrickeln kann ich natürlich, im Zweifelsfall mit
einer Speicherbarriere. Aber mich würd doch interessieren, was
eigentlich der 'legale' Weg ist.
Sven P. schrieb:> Und könntest du das auch bitte irgendwie, am besten anhand des> C-Standards, belegen...? Das wäre super.
C99 6.7.3.6: An object that has volatile-qualified type may be modified
in ways unknown to the implementation or have other unknown side
effects. Therefore any expression referring to such an object shall be
evaluated strictly according to the rules of the abstract machine, as
described in 5.1.2.3. Furthermore, at every sequence point the value
last stored in the object shall agree with that prescribed by the
abstract machine, except as modified by the unknown factors mentioned
previously.
Sven P. schrieb:> Stefan Ernst schrieb:>> Eine weitere Möglichkeit auf Nummer Sicher zu gehen wäre, aus den zwei>> Anweisungen eine zu machen, und mit einem Sequence-Point darin, der die>> Reihenfolge garantiert:>>
1
buffer='X',transmit=1;
IMO ist das so sogar besser lesbar,
>> weil es die direkte Zusammengehörigkeit und Reihenfolgeabhängigkeit der>> beiden Zuweisungen dokumentiert.>
Das kann man zwar schreiben (auch weil es m.E. die Lesbarkeit erhöht),
volatile deklarieren sollte/muß man die Variablen aber trotzdem.
Der Sequence-Point, der durch den Komma-Operator gesetzt wird,
garantiert nur, daß alle Statements vor dem Komma abgeschlossen sind,
bevor die nach dem Komma ausgeführt werden. Wenn der Compiler nicht
"sieht" (über volatile), daß diese Statements in jedem Falle ausgeführt
werden müssen, weil die Variablen potentiell außerhalb des Programmes
verändert wurden bzw. die Zuweisung einen ihm unbekannten Seiteneffekt
hat, darf er immer noch beschließen, die entsprechenden (aus seiner
Sicht unnötigen) Statements komplett wegzuoptimieren.Im Beispiel: wenn
der Compiler der Ansicht ist, daß buffer bereits den Wert 'X' hat, wird
er den entsprechenden Code gar nicht generieren.
Stefan Ernst schrieb:> Eine weitere Möglichkeit auf Nummer Sicher zu gehen wäre, aus den zwei> Anweisungen eine zu machen, und mit einem Sequence-Point darin, der die> Reihenfolge garantiert:>
1
buffer='X',transmit=1;
> IMO ist das so sogar besser lesbar,> weil es die direkte Zusammengehörigkeit und Reihenfolgeabhängigkeit der> beiden Zuweisungen dokumentiert.
Das ist schlecht, weil kaum jemand die Feinheiten des Komma-Operators
kennt.
Besser als zwei Anweisungen lassen und ggf. einen Kommentar schreiben.
Sven P. schrieb:> Drumherumfrickeln kann ich natürlich, im Zweifelsfall mit>> einer Speicherbarriere. Aber mich würd doch interessieren, was>> eigentlich der 'legale' Weg ist.
Das ist nicht "frickeln", sondern der einzig richtige und vernünftige
Weg.
Peter II schrieb:> Sven P. schrieb:>>> Beide Variablen sind als volatile gekennzeichnet. Aber bewirkt das>>> (standardkonform), dass die beiden Zuweiseungen nicht umsortiert>>> werden...?>>>> ja
Falsch. volatile verhindert so erstmal kein Umsortieren.
Ich habe weitergelesen. Wenn ich das richtig interpretiere, was der
Standard da formuliert, dann müsste das hier doch zu Problemem führen:
1
staticu8tx_buff[TX0_SIZE];
2
staticu8tx_in;
3
staticu8tx_out;
4
5
#define vu8(x) (*(volatile u8*)&(x))
6
#define ROLLOVER( x, max ) x = ++x >= max ? 0 : x
7
// count up and wrap around
8
9
ISR(USART0_UDRE_vect)
10
{
11
if(tx_in==tx_out){// nothing to sent
12
UTX0_IEN=0;// disable TX interrupt
13
return;
14
}
15
UDR0=tx_buff[tx_out];
16
ROLLOVER(tx_out,TX0_SIZE);
17
}
18
19
voiduputchar0(u8c)
20
{
21
u8i=tx_in;
22
23
ROLLOVER(i,TX0_SIZE);
24
/*1*/tx_buff[tx_in]=c;
25
while(i==vu8(tx_out));// until at least one byte free
26
// tx_out modified by interrupt !
27
tx_in=i;
28
/*2*/UTX0_IEN=1;// enable TX interrupt
29
}
30
31
voiduputs0_(u8*s)
32
{
33
while(*s)
34
uputchar0(*s++);
35
}
Es stammt auszugsweise aus Peter Danneggers USART-Ringpuffer. Die
übrigen Definitionen finden sich im Header in Peters Archiv:
Beitrag "AVR-GCC: UART mit FIFO"
In 'uputchar0' habe ich eine Zeile (1) markiert, in der in den
Ringpuffer geschrieben wird. Der ist nicht als 'volatile' qualifiziert,
genausowenig wie 'tx_in'. Es ist dem Compiler also m.M.n. erlaubt, die
Zuweisungen herumzuschieben. Unter anderem dürfte er die auch erst nach
der markierten Zeile (2) ausführen - das wäre entsprechend fatal.
Ein anderer Effekt würde sich zeigen, wenn 'uputchar0' gleich ganz als
inline-Funktion umgesetzt wird, etwa in 'uputs0_'. Das darf der Compiler
ja auch. Dann nämlich könnte er bis zum Ende der Schleife warten, bis
tx_in wieder ins Ram geschrieben und damit für den Interrupt sichtbar
wird. Dann würde der Interrupt garnicht merken, dass der Puffer sich
füllt, also letztlich Deadlock.
M.M.n. müssten der Puffer und 'tx_in' wenigstens als 'volatile'
qualifiziert sein. Oder alternativ müsste man vor dem Einschalten des
Interrupts eine Memory Barrier einziehen.
Ich habe Peter bereits gefragt, ob er sich da mehr bei gedacht hat. Aber
der antwortet nicht.
Charlie schrieb:>> ja>> Falsch. volatile verhindert so erstmal kein Umsortieren.
Jetzt bin ich wieder verwirrt.
Das wäre ja dann höchst fatal!
Ich könnte ja nichtmal mehr die Peripherie des Mikroprozessors bedienen,
wenn die Zugriffe eigentlich umsortiert werden können...
Vielleicht etwas präziser:
1
volatileinta;
2
intb;
3
volatileintc;
4
5
a=1;
6
b=2;
7
c=3;
Wenn ich das strikt nach der oben zitierten Passage aus dem Standard
interpretiere, habe ich mindestens drei Sequenzpunkte erzeugt, nämlich
jeweils einen nach jeder der beiden Zuweisungen.
Der Standard sagt dann, dass der Wert, der auf das Objekt gespeichert
ist, beim Sequenzpunkt mit der Vorstellung der 'abstrakten' Maschine
übereinstimmt. Das müsste aber dann doch bedeuten, dass der Compiler im
Beispiel hier zwar die Zuweisung an 'c' beliebig um die beiden anderen
Zuweisungen verschieben darf. Aber die Abfolge der Zuweisungen an 'a'
und 'b' muss gewahrt bleiben, d.h. 'b' darf seinen Wert nicht vor 'a'
bekommen.
Oder...?
Ja, wenn du einen Sequenzpunkt erzeugst. Das machst du aber zum Beispiel
nicht bei reinem Lesen.
Ich will auch nur dem verbreiteten Irrglauben entgegentreten, volatile
sei das Allheilmittel für Reordering und exklusiven Zugriff.
Aber das Forum erzieht die Leute ja so: volatile deklarieren und ISR und
main können beide exklusiv zugreifen...
Charlie schrieb:> Ja, wenn du einen Sequenzpunkt erzeugst. Das machst du aber zum Beispiel> nicht bei reinem Lesen.
Hm. Eigentlich doch schon...?!
Der Standard sagt, ein Sequenzpunkt ist nach der expression in einem
expression statement. Eine expression kann aber beispielsweise auch
die conditional expression sein. Diese kann man bis zur /cast
expression/ durchklopfen, die dann etwa sowas sein darf:
1
(void)REGISTER;
Das wäre also schon ein Sequenzpunkt. Denkfehler?
> Aber das Forum erzieht die Leute ja so: volatile deklarieren und ISR und> main können beide exklusiv zugreifen...
Davon will ich mich distanzieren...
Was hälst du von der Geschichte mit dem UART-Puffer?
Über Code-reordering als fehlerquelle bin ich noch nie gestolpert. Denn
die Ausführung wie oben hängt von zwei von einander abhängigen Variablen
ab, die beide volatile sein sollten.
Bei nicht verschachtelten ISRs reichte ausserhalb des ISR-Contextes
immer aus, den volatilen Zugriff mittels __disable_interrupt und
__enable_interrupt einzurahmen.
Coder schrieb:> Über Code-reordering als fehlerquelle bin ich noch nie gestolpert. Denn> die Ausführung wie oben hängt von zwei von einander abhängigen Variablen> ab, die beide volatile sein sollten.
In meinem Beispiel ja. Das kann ich auch anhand des Standards
nachvollziehen.
Aber im UART-Puffer-Quelltext von Peter?
-> Beitrag "Re: Volatile-Zugriffe umsortieren"
Da macht es mir Sorgen...
IMHO würde ich die Variablen als volatile deklarierten und die Zugriffe
so gestalten wie geschrieben. Auf einem AVR können 8-bit Zurgiffe atomic
sein, da zum Speichern glaube ich nur eine instruktion nötig ist. Man
bemerke dass er in der Schleife nicht ohne Grund einen volatile cast
macht.
Charlie schrieb:> Ja, wenn du einen Sequenzpunkt erzeugst. Das machst du aber zum Beispiel> nicht bei reinem Lesen.
Volatile bezieht sich nicht nur auf Schreibzugriffe. Auch Lesezugriffe
sind betroffen. Aber eben nur zwischen diversen volatile Zugriffen
untereinander, nicht zwischen einem Mix von volatile und non-volatile.
Mein obiges beispiel ist Müll. Sorry
Also ich habe struct
1
typedefstruct
2
{
3
int16_ta;
4
int16_tb;
5
int16_tc;
6
}test_t
die ich so nutze
1
volatiletest_ttest.
2
3
test.a=10;
4
test.c=30;
5
test.b=20;
ich würde erwarten, dass der Compiler dies zu
1
test.a=10;
2
test.b=30;
3
test.c=20;
optimiert (reorder), da so der Zugriff in diesem Beispiel effektiver
ist. Und er KANN es machen , weil die Variablen im Ergebnis bzw.
Verwendung unabhängig von der Reihenfolge der Ausführung sind. Egal ob
es volatile ist oder nicht.
Coder schrieb:> Wäre hier Reordering möglich?
Nein. Falls doch, ist es ein Compilerfehler wie etwa PR51374 im GCC:
http://gcc.gnu.org/PR51374
Wer einen avr-gcc 4.6.2 einsetzt, sollte das auch "live" nachvollziehen
können :-)
Coder schrieb:> ich würde erwarten, dass der Compiler dies zu [...]> optimiert (reorder),
Ich nicht.
> Und er KANN es machen , weil die Variablen im Ergebnis bzw.> Verwendung unabhängig von der Reihenfolge der Ausführung sind.
Nein. Er mag es vielleicht können, aber nicht dürfen.
Falls es dir um
volatile struct { ... }
gegenüber
struct { volatile ... }
geht: Das ist äquivalent.
Abgesehen davon sind
test.a =10;
test.c =30;
test.b =20;
und
test.a =10;
test.b =30;
test.c =20;
unabhängig von volatile im Ergebnis nicht identisch. Tippfehler?
Ja. Tippfehler. Bevor das jetzt hier ausartet, werde ich mir das
gcc-manual durchlesen und die Suchmaschine benutzen. Verdammt, ich will
das jetzt wissen...
Ich auch :-)
Also mit deiner Struktur kann ich weiterhelfen.
ISO/IEC8999:1999TC2 sagt in §6.5.2.3 Abs. 7: Wenn man eine Variable mit
Strukturtyp als 'volatile' qualifiziert, schlägt die Qualifizierung
quasi auf alle Elemente der Struktur durch.
Und die Zuweisungen an 'volatile'-qualifizierte Variablen dürfen nicht
vertauscht werden, das hatten wir ja eingangs geklärt.
Johann L. schrieb:>> geht: Das ist äquivalent.>> Jein. Wiederum ist das "Implementation defined".
Ok, "äquivalent" mag übertrieben sein. Und volatile Bitfelder sind ein
Thema für sich, auf die bezieht sich der Thread hier bisher nicht.
Wie Sven oben schon erwähnte erscheint mir §6.5.2.3.7 als ziemlich
eindeutig und nicht "implementation defined".
Johann L. schrieb:> Ist volatile nicht "Implementation defined"?> http://gcc.gnu.org/onlinedocs/gcc/Qualifiers-implementation.html
Dieser Abschnitt im GCC Manual und im Standard lässt nur die Frage
offen, inwieweit Ausdrücke ohne Verwendung des Ergebnisses, wie eben
*src;
einen Lesezugriff darstellen, oder bezogen auf das Ziel garnix.
Auch darum geht es im diesem Thread nicht.
In obigem
transmit = 1;
ist der Sachverhalt wohl unstrittig.
Wahrscheinlich hat man vergessen, den Standard durch Patentanwälte
korrekturlesen zu lassen. Und nun kommst du an, um durch dieses Nadelöhr
ein Kamel zu schleusen. ;-)
A. K. schrieb:> Johann L. schrieb:>> Ist volatile nicht "Implementation defined"?>> http://gcc.gnu.org/onlinedocs/gcc/Qualifiers-implementation.html>> Dieser Abschnitt im GCC Manual und im Standard lässt nur die Frage> offen, inwieweit Ausdrücke ohne Verwendung des Ergebnisses, wie eben> *src;> einen Lesezugriff darstellen, oder bezogen auf das Ziel garnix.> Auch darum geht es im diesem Thread nicht.
Auch das ist m.M.n. im Standard klar geregelt.
Schau mal im Anhang C: Dort stehen die Sequenzpunkte beschrieben.
Und dann schau im Anhang A.2 in die Grammatik. Dort kannst du vom
'expression statement', welches ja einen Sequenzpunkt darstellt, bis
hinauf zur 'primary expression' durchwandern, welche dann sowas wie
'*src' ist.
Zumindest wenn 'src' volatile ist, sollte das eindeutig einen Zugriff
bezeichnen, oder?
Sven P. schrieb:> Zumindest wenn 'src' volatile ist, sollte das eindeutig einen Zugriff> bezeichnen, oder?
Es geht um den Fall
volatile int *src; *src;
Also um einen Zugriff auf "*src", nicht auf src.
Zumindest fanden es die Autoren von GCC nicht klar, ob das per Standard
nun einen Zugriff darstellen soll, oder nicht.
Sven P. schrieb:> Ich habe Peter bereits gefragt, ob er sich da mehr bei gedacht hat.
Ich habe mit Keil C51 angefangen, da gab es kein Umsortieren. Ich weiß
garnicht, ob der volatile überhaupt kennt.
Die ersten AVR-GCC haben auch wenig umsortiert oder wegoptimiert. Daß
man volatile braucht, kam erst viel später. Es kann daher gut sein, daß
in meinen älteren Beispielen mal ein volatile zu wenig ist.
Da ich gerne kleinen Code haben will, habe ich auch immer das
unerwartete Inlinen abgeschaltet (ist default leider an). Somit werden
auch kleine Funktionen aufgerufen und über Funktionsgrenzen darf ja
nicht umsortiert werden.
Peter
A. K. schrieb:> Sven P. schrieb:>> Zumindest wenn 'src' volatile ist, sollte das eindeutig einen Zugriff>> bezeichnen, oder?>> Es geht um den Fall> volatile int *src; *src;> Also um einen Zugriff auf "*src", nicht auf src.>> Zumindest fanden es die Autoren von GCC nicht klar, ob das per Standard> nun einen Zugriff darstellen soll, oder nicht.
Achso, nun hab ich das.
Das würde ja bedeuten, dass folgende drei Varianten jeweils semantisch
verschieden sind?!
1
UDR;
2
3
(void)UDR;
4
5
chardummy;
6
dummy=UDR;
7
/* dummy wird ansonsten nicht mehr benutzt */
Ansonsten leuchtet die Syntax mir ja ein. Ich kann 'const int *'
schreiben und dann ist das konstant, worauf der Zeiger zeigt. Oder aber
int * const', dann kann ich den Zeiger nicht verschieben, aber dennoch
die Daten verändern, auf die er zeigt. Über Sinn und Unsinn, den ein
analoges Verhalten bei 'volatile' hätte, kann man freilich debattieren.
Peter Dannegger schrieb:> über Funktionsgrenzen darf ja> nicht umsortiert werden
Bist du da sicher? Funktionsgrenzen von Anwenderfunktionen gehören nicht
zum 'observable behaviour', mit welchem der Standard argumentiert. Es
spricht also eigentlich nichts dagegen, auch über die Funktionsgrenzen
hinweg zu optimieren. Die meisten Compiler werden sicherlich spätestens
dann aufgeben, wenn der Code in verschiedenen Objekten liegt, aber ob
inline oder nicht, das sollte eigentlich keinen Einfluss auf das
Verhalten des Programms haben...
Oh je. Was war das alles so einfach, als mir das alles noch egal war :-}
Man merkt deutlich, daß C nicht für Echtzeitapplikationen gemacht wurde.
In MC-Anwendungen kommt es aber oft darauf an, daß der Zeitpunkt und die
Reihenfolge exakt eingehalten werden.
Das volatile finde ich auch eher als Krücke.
Es müßte einen Compilerschalter geben, mit dem man das Umsortieren
verbieten kann. Oder zumindest ein Statement, wo man das für einen Block
verbieten kann.
Peter
Peter Dannegger schrieb:> Ich habe mit Keil C51 angefangen, da gab es kein Umsortieren. Ich weiß> garnicht, ob der volatile überhaupt kennt.
Anfang der 1990er hatte ich mit C51 (3.2x bis 3.40) gearbeitet. Hab
gerade mal in die uralten Quelltexte geschaut: Es gibt ein paar als
volatile definierte Variable, die sowohl aus dem normalen Programmfluss
als auch aus einer ISR genutzt wurden.
Grüße
Stefan
Peter Dannegger schrieb:> Es müßte einen Compilerschalter geben, mit dem man das Umsortieren> verbieten kann. Oder zumindest ein Statement, wo man das für einen Block> verbieten kann.
Dann bist du aber schon ziemlich dicht dran, gleich einen Assembler zu
benutzen...
Peter Dannegger schrieb:> Es müßte einen Compilerschalter geben, mit dem man das Umsortieren> verbieten kann. Oder zumindest ein Statement, wo man das für einen Block> verbieten kann.
Gibt es beim avr-gcc nicht ein Pragma, um eine "memory barrier" zu
setzen?
Wenn ich das Konzept richtig verstanden habe, passiert da genau das, was
du suchst.
Grüße
Stefan
Stefan Wagner schrieb:> Peter Dannegger schrieb:>> Es müßte einen Compilerschalter geben, mit dem man das Umsortieren>> verbieten kann. Oder zumindest ein Statement, wo man das für einen Block>> verbieten kann.>> Gibt es beim avr-gcc nicht ein Pragma, um eine "memory barrier" zu> setzen?
Im GCC reicht es, zu clobbern:
1
asmvolatile("":::"memory");
Der GCC hat sicherlich auch builtins dafür (__sync_synchronize oder so).
Sven P. schrieb:> Oh je. Was war das alles so einfach, als mir das alles noch egal war :-}
Auf diesen Satz hast du Copyright! Der ist einmalig und kommt in meine
Sprüche-Sammlung :-]
Warum wird hier eigentlich im gleichen Zusammenhang gesagt dass man den
C-Standard und nicht die Implementierung zur Beantwortung heranziehen
soll... und dann beschrieben wie man mit implementierungsabhängigen
Mitteln das gewünschte erreicht :)
Optimierung mit -O2 enthält (u.a.)
-fschedule-insns (Reorders instructions to minimize execution stalls)
Das reordering wird mit mehreren -fsched-... oder -fschedule-... options
gesteuert. Welche könnte man verwenden?
Naja das war jetzt viel lärm um nicht so viel Meine Zusammenfassung ist,
dass wenn man bei Compiler-Optmimierungen nicht erwarten kann, dass der
Programmcode so ausgeführt wird, wie er als C Code geschrieben ist;
Reordering hin oder her.
Peter Dannegger schrieb:> Man merkt deutlich, daß C nicht für Echtzeitapplikationen gemacht wurde.> In MC-Anwendungen kommt es aber oft darauf an, daß der Zeitpunkt und die> Reihenfolge exakt eingehalten werden.>> Das volatile finde ich auch eher als Krücke.
Genau dafür ist es aber gedacht und macht auch genau das, was man will,
wenn man's richtig anwendet. Irgendwie beginnt dieser Thread, sich
langsam im Kreis zu drehen ;).
> Es müßte einen Compilerschalter geben, mit dem man das Umsortieren> verbieten kann. Oder zumindest ein Statement, wo man das für einen Block> verbieten kann.>
Den gibt's. Zwar nicht im Standard, aber gcc tut meist das, was man
erwartet: ohne Optimierung übersetzen.
Wenn >= gcc 4.4, kann man auch so was machen:
1
...
2
3
#pragma GCC push_options
4
#pragma GCC optimize "O0" /* oder "no-wasauchimmer" */
5
6
/* unoptimierter code */
7
...
8
#pragma GCC pop_options
9
10
/* Code wieder so optimiert wie in den Copmpileroptionen global gesetzt */