Wir wissen ja alle, Variablen sind in C bzw. C++ als volatile zu
kennzeichnen, sobald sie sowohl im normalen Programmablauf als auch in
einer ISR verwendet werden. Zu den Feinheiten der Interpretation gibt es
hier ja auch etliche Threads.
Nun bin ich in der Situation, dass ich volatile in einem Array
theoretisch verwenden müsste. Ich würde es aber gern vermeiden, denn das
Array ist groß und es werden sehr oft einzelne Elemente in verschiedenen
Funktionen gelesen oder geändert. Nun würde in meinem Fall niemals jmd
auf die Idee kommen, in einer ISR etwas am Array zu ändern. Hier wird
brav nur gelesen. Also ist damit ja EIGENTLICH bereits ausgeschlossen,
dass es während eines Zugriffs zu irgendeiner Unterbrechung kommt, nach
der das Array anders aussieht.
Gibt es Gründe, trzd als volatile zu deklarieren?
Ohne volatile ist nicht sicher, dass im C Quellcode geschriebene Daten
auch wirklich zu diesem Zeitpunkt im Speicher stehen. Das kann sich
beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht.
Rolf Rolfus schrieb:> Wir wissen ja alle, Variablen sind in C bzw. C++ als volatile zu> kennzeichnen, sobald sie sowohl im normalen Programmablauf als auch in> einer ISR verwendet werden. Zu den Feinheiten der Interpretation gibt es> hier ja auch etliche Threads.>> Nun bin ich in der Situation, dass ich volatile in einem Array> theoretisch verwenden müsste. Ich würde es aber gern vermeiden, denn das> Array ist groß und es werden sehr oft einzelne Elemente in verschiedenen> Funktionen gelesen oder geändert. Nun würde in meinem Fall niemals jmd> auf die Idee kommen, in einer ISR etwas am Array zu ändern. Hier wird> brav nur gelesen. Also ist damit ja EIGENTLICH bereits ausgeschlossen,> dass es während eines Zugriffs zu irgendeiner Unterbrechung kommt, nach> der das Array anders aussieht.>>> Gibt es Gründe, trzd als volatile zu deklarieren?
Wenn du mal scharf nachdenkst WARUM man die Variable als volatile
Kennzeichnet wenn man sie im Programmablauf und der ISR verwendet,
solltest du drauf kommen.
Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array
komplett wegzuoptimieren wenn es nur in der ISR gelesen wird?
Tim T. schrieb:>> Wenn du mal scharf nachdenkst WARUM man die Variable als volatile> Kennzeichnet wenn man sie im Programmablauf und der ISR verwendet,> solltest du drauf kommen.> Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array> komplett wegzuoptimieren wenn es nur in der ISR gelesen wird?
Wenn du richtig lesen würdest, könntest du dir solche Antworten sparen.
Es steht nirgends, dass das Array nur in ISRs gelesen wird. Da steht es
wird in ISRs nur gelesen. Kleiner aber feiner Unterschied. :)
A. K. schrieb:> Ohne volatile ist nicht sicher, dass im C Quellcode geschriebene> Daten> auch wirklich zu diesem Zeitpunkt im Speicher stehen. Das kann sich> beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht.
Sehr guter Punkt! Danke!
Tim T. schrieb:> Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array> komplett wegzuoptimieren wenn es nur in der ISR gelesen wird?
Das ist der feine Unterschied zwischen "... wenn ISR nur liest" und
"... wenn nur ISR liest"
Ohne volatile kannst du auch nach jedem Compilerlauf im Listing prüfen,
ob die von dir gewünschte Funktionalität gegeben ist.
Nervt etwas aber belohnt mit Performance.
my2ct schrieb:> Tim T. schrieb:>> Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array>> komplett wegzuoptimieren wenn es nur in der ISR gelesen wird?>> Das ist der feine Unterschied zwischen "... wenn ISR nur liest" und> "... wenn nur ISR liest"
Stimmt.
A. K. schrieb:> Das kann sich> beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht.
Beliebig sicher nicht. Stichwort ist "Sequence point". Da eine ISR von
der Natur der Sache nach asynchron ist, dürfte das also unerheblich
sein. Die Wirkung auf das Programm, wenn das ganze Array volatile ist,
scheint mir schlimmer.
MfG Klaus
Rolf Rolfus schrieb:> Nun würde in meinem Fall niemals jmd> auf die Idee kommen, in einer ISR etwas am Array zu ändern. Hier wird> brav nur gelesen.
So what?
Die ISR unterbricht main zu jedem beliebigen Zeitpunkt. Wenn also main
irgendwas am Datenbestand ändert, kann sieht die ISR eben keine
konsistenten Datenstand mehr.
Volatile hilft dagegen allerdings nicht. Nur eine Interruptsperre über
alle Änderungen am Datenbestand hinweg, bis wieder ein in sich
konsistenter Inhalt garantiert ist, den die ISR lesen darf.
Sprich (vereinfacht): die Interruptsperre stellt sozusagen das
Gegenstück zu volatile dar. Das eine garantiert die Datenkonsistenz bei
der Datenrichtung main->ISR, das andere dasselbe für den umgekehrten
Weg.
Das Hauptproblem ist: der Programmierer muss festlegen, was ein
"konsistenter Datenbestand" ist. Und über alle Verwicklungen in main
hinweg sicherstellen, dass nie die ISR zuschlägt, wenn er halt nicht
konsistent ist.
Das führt in C leider oftmals zu unerträglich langen Interruptsperren,
deren Maximaldauer obendrein noch schwer bis nicht vorhersagbar ist.
Assembler rules...
c-hater schrieb:> …
Wie du schon selbst gemerkt hast, ist das ein ganz anderes Thema. Hier
geht es um volatile und nicht um atomaren Zugriff. Der ist
sichergestellt...
Klaus schrieb:> Beliebig sicher nicht. Stichwort ist "Sequence point".
Nein, ein sequence point hilft hier nicht weiter. Der garantiert nur,
dass im bei Code
a; b; c;
in alle Änderungen in a auch in b und c sichtbar sind. Da der Compiler
den Datenfluss in a,b,c kennt, darf er Daten in Registern behalten, die
dem Quellcode nach eigentlich bereits in a im Speicher landen sollten.
Er muss nur darauf achten, dass in b und c dann nicht auf den Speicher,
sondern auf das Register zugegriffen wird. Eine ISR wird auf den
Speicher zugreifen und die Altdaten sehen.
Klaus schrieb:> Beliebig sicher nicht. Stichwort ist "Sequence point". Da eine ISR von> der Natur der Sache nach asynchron ist, dürfte das also unerheblich> sein. Die Wirkung auf das Programm, wenn das ganze Array volatile ist,> scheint mir schlimmer.>> MfG Klaus
Hmm, interessant. Aber diese Sequence Points zu bestimmen ist wieder so
ein Thema für sich oder? Klingt auf den ersten Blick auch nicht gerade
trivial.
Rolf Rolfus schrieb:> Hmm, interessant. Aber diese Sequence Points zu bestimmen ist wieder so> ein Thema für sich oder? Klingt auf den ersten Blick auch nicht gerade> trivial.
Das ist relativ einfach, aber hier kaum relevant. In
i++ + i++
ist unklar, was dabei rauskommt, weil kein sequence point dazwischen
ist. In
i++; i++;
ist sichergestellt, dass der zweite Teil die aktualisierte Version vom
ersten Teil sieht.
Mit einer ISR hat das aber nichts zu tun, weil der ganze Code im
Register ablaufen darf und die ISR solche Änderungen von i dann nicht
mitbekommt.
A. K. schrieb:> Nein, ein sequence point hilft hier nicht weiter. Der garantiert nur,> dass im bei Code> a; b; c;> in alle Änderungen in a auch in b und c sichtbar sind.
Hier geht es um ein Array und die ISR liest nur ein Element davon. Wenn
ein Teil des Arrays nur in Registern aktuell ist, würde auch jede
Funktion, die möglicherweise mit dem Array zu tun hat (was am Ende erst
der Linker weiß), nicht funktionieren, unabhängig ob es eine ISR ist
oder nicht.
MfG Klaus
Klaus schrieb:> Hier geht es um ein Array und die ISR liest nur ein Element davon. Wenn> ein Teil des Arrays nur in Registern aktuell ist, würde auch jede> Funktion, die möglicherweise mit dem Array zu tun hat (was am Ende erst> der Linker weiß), nicht funktionieren, unabhängig ob es eine ISR ist> oder nicht.
Der für den Compiler erkennbare Aufruf einer Funktion, deren Code der
Compiler nicht kennt, sorgt dafür, dass er vor dem Aufruf alle
"hängenden" Daten, die diese Funktion potentiell nutzen könnte, in den
Speicher befördert.
Der Unterschied zu einer ISR besteht darin, dass der Compiler keine
Ahnung davon hat, dass überall im Code die ISR aufgerufen werden könnte.
Folglich berücksichtigt er sie nicht.
Gibt es in C keine "Optimierungsgrenzen" (Englisch Memory Barrier)? D.h.
dass der Compiler bspw. nicht ueber die Funktionen hinweg optimieren
darf. Da es sich um eine globale Variable handelt waere so
sichergestellt, dass die Variable zeitnah geschrieben wird. Trotzdem
kann der Compiler lokale Optimierungen durchfuehren.
Das nur mal als Denkanstoss, kenne mich mit C nicht aus.
A. K. schrieb:> Der Unterschied zu einer ISR besteht darin, dass der Compiler keine> Ahnung davon hat, dass überall im Code die ISR aufgerufen werden könnte.> Folglich berücksichtigt er sie nicht.
Das ist schon klar. Es geht aber um
A. K. schrieb:> Das kann sich beliebig verzögern,
das beliebig hier. Da die ISR nur ein Element aus dem Array liest, ist
höchstens die Verzögerung ein Problem.
MfG Klaus
Klaus schrieb:> Das ist schon klar. Es geht aber um>> A. K. schrieb:>> Das kann sich beliebig verzögern,>> das beliebig hier. Da die ISR nur ein Element aus dem Array liest, ist> höchstens die Verzögerung ein Problem.
1
staticinta[2];
2
3
intmain()
4
{
5
for(;;)
6
{
7
if(PINB)
8
{
9
a[0]=1;
10
}
11
12
if(a[0])
13
{
14
tu_was_anderes();
15
a[0]=0;
16
}
17
}
18
}
Was hindert den Compiler daran, a[0] komplett über die ganze Laufzeit in
einem Register zu halten? Dass bei einem Array, dessen Größe über die
Anzahl der frei verfügbaren Register hinausgeht, das anders aussehen
kann, macht trotzdem die allgemeine Aussage von A.K.
> Das kann sich beliebig verzögern,
nicht falsch.
Frank M. schrieb:> Was hindert den Compiler daran, a[0] komplett über die ganze Laufzeit in> einem Register zu halten? Dass bei einem Array, dessen Größe über die> Anzahl der frei verfügbaren Register hinausgeht, das anders aussehen> kann, macht trotzdem die allgemeine Aussage von A.K.>>> Das kann sich beliebig verzögern,>> nicht falsch.Rolf Rolfus schrieb:> Ich würde es aber gern vermeiden, denn das Array ist *groß*
Aber ok, der spitzfindigere hat gewonnen.
MfG Klaus
Rolf Rolfus schrieb:> Okay vielen Dank! Dann wird volatile also auf jeden Fall gesetzt> und ich versuche das Drumherum dafür zu optimieren.
Das typische Pattern dafür geht über Schattenvariablen. Also Du ziehst
Dir aus dem Array einen Eintrag in eine lokale Variable, die nicht
volatile ist, arbeitest damit, und am Ende schreibst Du das wieder
zurück ins Array.
Und zwar sowohl im Hauptprogramm als auch in der ISR.
Zum ausprobieren:
Definiere ein Array für eine 7 Seg. Displayanzeige die mit Multiplex
arbeitet.
Ändere unzyklisch in der Main die Daten im Array...
Ohne volatile wird die nur lesende ISR ziemlichen Schrott auf das
Display pinseln.
Klaus schrieb:> A. K. schrieb:>> Das kann sich>> beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht.>> Beliebig sicher nicht.
Doch.
> Stichwort ist "Sequence point".
Die haben damit erstmal nichts zu tun.
1
x=3;
2
x=5;
3
x=7;
Der Compiler kann von den obigen drei Zeilen die ersten beiden
wegoptimieren und die dritte beliebig weit nach hinten schieben, ggf.
auch komplett wegoptimieren und beim nächsten Lesevorgang einfach die 7
direkt als Konstante einfügen. Und das, obwohl da drei Sequenzpunkte
drin sind.
Sequenzpunkte wirken sich nur auf die abstrakte Maschine aus, nicht
zwingend auf den RAM des physischen Systems.
> Da eine ISR von der Natur der Sache nach asynchron ist, dürfte das also> unerheblich sein. Die Wirkung auf das Programm, wenn das ganze Array> volatile ist, scheint mir schlimmer.
Ein langsames Programm ist für mich weniger schlimm als ein nicht
funktionierendes Programm.
c-hater schrieb:> Volatile hilft dagegen allerdings nicht.
Kleine Ergänzung: volatile alleine hilft dagegen nicht. Es ist
notwendig, aber nicht hinreichend.
Klaus schrieb:> Frank M. schrieb:>> Was hindert den Compiler daran, a[0] komplett über die ganze Laufzeit in>> einem Register zu halten? Dass bei einem Array, dessen Größe über die>> Anzahl der frei verfügbaren Register hinausgeht, das anders aussehen>> kann, macht trotzdem die allgemeine Aussage von A.K.>>>>> Das kann sich beliebig verzögern,>>>> nicht falsch.>> Rolf Rolfus schrieb:>> Ich würde es aber gern vermeiden, denn das Array ist *groß*>> Aber ok, der spitzfindigere hat gewonnen.
Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code
auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem
Register halten und nie in den Speicher rausschreiben.
Rolf Rolfus schrieb:> Gibt es Gründe, trzd als volatile zu deklarieren?
Das volatile ist für den Interrupt uninteressant, sondern nur für das
Main wichtig. Es sagt dem Main, daß der Zugriff erfolgen muß, d.h. auch
ein Schreibzugriff.
Man kann auch nur für das Main über ein Macro nach volatile casten, so
daß der Interrupt seine Zugriffe weiterhin optimieren kann.
Peter D. schrieb:> Rolf Rolfus schrieb:>> Gibt es Gründe, trzd als volatile zu deklarieren?>> Das volatile ist für den Interrupt uninteressant, sondern nur für das> Main wichtig.
Es ist unwahrscheinlich, dass der Compiler den Zugriff in der ISR
wegoptimiert, aber vom Prinzip her ist volatile dort genauso nötig wie
in main.
Rolf M. schrieb:> Es ist unwahrscheinlich, dass der Compiler den Zugriff in der ISR> wegoptimiert, aber vom Prinzip her ist volatile dort genauso nötig wie> in main.
Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim
Verlassen speichern, also auch ein Interrupt.
Nur die Mainloop kann es wegoptimieren, da sie ja nie verlassen wird.
Einmalig aufgerufene oder kurze Unterfunktionen werden in Regel vom
Optimizer geinlined, gehören also quasi mit zur Mainloop.
Ein Sonderfall ist möglich, wenn 2 Interrupts unterschiedlicher
Priorität die selbe Variable zugreifen. Dann muß der Interrupt
niedrigerer Priorität auch volatile kapseln, damit er die Änderungen
mitkriegt, wenn er selber unterbrochen wird.
Rolf M. schrieb:> Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code> auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem> Register halten und nie in den Speicher rausschreiben.
Ich versuche mir gerade vorzustellen, wie z.B. ein strlen() auf einem
Chararray abläuft, wenn ein Teil der Chars in Registern liegt. Aber du
wirst mir das gleich erklären (ich ahne: ein Program daß nur aus main()
und ISR besteht)
MfG Klaus
Peter D. schrieb:> Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim> Verlassen speichern, also auch ein Interrupt.
Den Verdacht hatte ich auch schon mal. Aber kann man das irgendwo
nachlesen, bzw. geht das aus irgendeiner Formulierung im Standard
zweifelsfrei hervor?
> Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim> Verlassen speichern, also auch ein Interrupt.
Ja, die ISR lies den Wert aus dem Speicher, aber der ist veraltet, weil
im Hauptprogramm der Wert nur im Register gehalten wird.
Und genau da hat man ein Problem.
Klaus schrieb:> Ich versuche mir gerade vorzustellen, wie z.B. ein strlen() auf einem> Chararray abläuft, wenn ein Teil der Chars in Registern liegt.
Die Funktion erhält als Parameter den Pointer auf das Array. Damit wird
dem Compiler klar, dass das Array vollständig im Speicher liegen muss.
Wobei die üblichen Aliasing-Regeln noch weitere Folgen haben, zumal ein
char* recht heftige Folgen für solche Optimierungen hat.
NB: Weshalb hältst Du C Compiler konsequent für strohdumm? Die wissen
zwar nicht, was Du tust, aber oft ganz gut, was sie selber tun. Das
haben Sie dem Menschen voraus, die haben nicht selten Probleme mit
beidem. ;-)
A. K. schrieb:> NB: Weshalb hältst Du C Compiler konsequent für strohdumm? Die wissen> zwar nicht, was Du tust, aber oft ganz gut, was sie selber tun. Das> haben Sie dem Menschen voraus, die haben nicht selten Probleme mit> beidem. ;-)
Sorry, die Idee, daß ein Element eines Arrays nie im RAM sondern nur im
Register lebt, stammt nicht von mir sondern von hier
Rolf M. schrieb:> Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code> auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem> Register halten und nie in den Speicher rausschreiben.Bastler schrieb:> Ja, die ISR lies den Wert aus dem Speicher, aber der ist veraltet, weil> im Hauptprogramm der Wert nur im Register gehalten wird.> Und genau da hat man ein Problem.
Ich hab nur versucht zu klären, wieviel Realität in dem immer bzw
nur steckt.
MfG Klaus
Rolf Rolfus schrieb:>> Nun bin ich in der Situation, dass ich volatile in einem Array> theoretisch verwenden müsste. Ich würde es aber gern vermeiden, denn das> Array ist groß und es werden sehr oft einzelne Elemente in verschiedenen> Funktionen gelesen oder geändert.
Wenn Du immer nur ein einzelnes Element liest, einen geänderten Wert
berechnest, und dann wieder wegschreibst, dann tut Dir das volatile auch
nicht weh. Du solltest dann nur darauf achten, Zwischenschritte bei der
Berechnung in lokalen Variablen zu machen.
Wenn z.B. koordiniert mehrere Werte geschrieben und dann durch Setzen
eines Flags als gültig markiert werden, helfen Speicherbarrieren (memory
barrier), damit das Flag wirklich nach den Datenwerten geschrieben wird
(bzw. dann vor ihnen gelesen wird). Beim Schreiben oder Lesen der Daten
kann der Compiler dann trotzdem optimieren und dabei z.B. die
Reihenfolge ändern.
Klaus schrieb:>> NB: Weshalb hältst Du C Compiler konsequent für strohdumm? Die wissen>> zwar nicht, was Du tust, aber oft ganz gut, was sie selber tun. Das>> haben Sie dem Menschen voraus, die haben nicht selten Probleme mit>> beidem. ;-)>> Sorry, die Idee, daß ein Element eines Arrays nie im RAM sondern nur im> Register lebt, stammt nicht von mir sondern von hier
Und auch von mir. Wenn der Compiler sich entschliesst, Daten in
Registern zu parken, dann weil er aus seiner Sicht weiss, dass dies
keine Probleme verursacht. Wenn der Compiler allerdings sieht, dass von
Daten die Adresse genommen wird, dann ists mehr oder weniger Essig mit
dieser Art von Optimierung und er lässt es bleiben. Deshalb ja das
Postulat, dass der Compiler weiss, was er tut.
> Ich hab nur versucht zu klären, wieviel Realität in dem immer bzw> nur steckt.
Wenn es deinem Seelenfrieden dient:
"Ohne volatile ist nicht sicher, dass im C Quellcode geschriebene Daten
auch wirklich zu diesem Zeitpunkt im Speicher stehen. Das kann sich umunbestimmte Zeit verzögern, so dass die ISR im Speicher noch die
Altdaten sieht."
A. K. schrieb:> Das kann sich um unbestimmte Zeit verzögern, so dass die ISR im Speicher> noch die Altdaten sieht."
Die ISR, da asynchron, liest immer alte Daten. Und die unbestimmte Zeit
ist auf jeden Fall zuende, wenn
A. K. schrieb:> Wenn der Compiler allerdings sieht, dass von Daten die Adresse genommen wird
oder eine Funktion aufgerufen wird, in der das globale Array
möglicherweise angefasst wird.
Damit ist ja alles klar.
MfG Klaus
Klaus schrieb:> Die ISR, da asynchron, liest immer alte Daten. Und die unbestimmte Zeit> ist auf jeden Fall zuende, wenn>> Damit ist ja alles klar.
Ich fürchte, dem ist nicht so. Denn auch dann ist bei
int a[2];
f(a);
a[i] = 1;
...2 Sekunden Beschäftigung...
a[i] = 2;
nicht sicher, dass 1 überhaupt geschrieben wird, wenn der Compiler
weiss, dass zwischendrin nichts mit a oder Pointern angestellt wird. Er
weiss zwar, dass auf a irgendwo zugegriffen werden könnte, weiss aber,
dass es nicht zwischen 1 und 2 geschieht. Optimierung kann
abschnittsweise völlig unterschiedlich ablaufen.
Aber in
int a[2];
f(a);
a[i] = 1;
g();
a[i] = 2;
muss er vor Aufruf von g() die 1 durchschreiben, wenn er den Code von
g() nicht kennt. Es kann sein, dass der in f(a) übergebene Pointer über
dunkle Kanäle nach g() gelangt und dort genutzt wird.
A. K. schrieb:> Es kann sein, dass der in f(a) übergebene Pointer über> dunkle Kanäle nach g() gelangt und dort genutzt wird.
Zu meinem Verständnis: könntest du das näher erläutern?
Wenn a[] und i nicht global sind und auch nicht in der Parameterliste
von g() auftauchen, welche dunklen Kanäle siehst du da noch?
HildeK schrieb:> A. K. schrieb:>> Es kann sein, dass der in f(a) übergebene Pointer über>> dunkle Kanäle nach g() gelangt und dort genutzt wird.>> Zu meinem Verständnis: könntest du das näher erläutern?> Wenn a[] und i nicht global sind und auch nicht in der Parameterliste> von g() auftauchen, welche dunklen Kanäle siehst du da noch?
int *p;
void f(int *ap) { p = ap; }
void g(void) { printf("%d", p[1]); }
A. K. schrieb:> Ich fürchte, dem ist nicht so. Denn auch dann ist bei> int a[2];> f(a);> a[i] = 1;> ...2 Sekunden Beschäftigung...> a[i] = 2;
Dazu muß der Compiler aber alle Funktionen, die in den 2 Sekunden
aufgerufen werden, komplett kennen um sicher zu wissen, das a[i] nie
geändert wird. Ich verstehe schon, was du meinst, halte es aber trotzdem
für eine Spitzfindigkeit, die für den TO ohne Bedeutung ist.
Rolf Rolfus schrieb:> und es werden sehr oft einzelne Elemente in verschiedenen> Funktionen gelesen oder geändert.
Und wenn dabei das i von a[i] auch noch zur Laufzeit berechnet wird,
bleibt einem Compiler, der fehlerfreien Code erzeugen will, nichts übrig
als ins reale Array zu schreiben.
MfG Klaus
Peter D. schrieb:> Rolf M. schrieb:>> Es ist unwahrscheinlich, dass der Compiler den Zugriff in der ISR>> wegoptimiert, aber vom Prinzip her ist volatile dort genauso nötig wie>> in main.>> Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim> Verlassen speichern, also auch ein Interrupt.
Mir wäre keine Regel bekannt, die sowas vorgibt.
Klaus schrieb:> Rolf M. schrieb:>> Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code>> auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem>> Register halten und nie in den Speicher rausschreiben.>> Ich versuche mir gerade vorzustellen, wie z.B. ein strlen() auf einem> Chararray abläuft, wenn ein Teil der Chars in Registern liegt. Aber du> wirst mir das gleich erklären (ich ahne: ein Program daß nur aus main()> und ISR besteht)
Wenn da noch ein strlen()-Aufruf dazwischen ist, wird der Optimizer das
natürlich berücksichtigen.
Klaus schrieb:> Ich hab nur versucht zu klären, wieviel Realität in dem immer bzw> nur steckt.
Das "immer" bezog sich genau auf das obige Progamm, nicht auf jedes
beliebige. Wenn man das Programm ändert, ist es natürlich möglich, dass
sich auch das Verhalten ändert.
Wenn ein Programm eine Schleife enthält, in der von einem Array immer
das selbe Element je Durchlauf einmal gelesen und einmal geschrieben
wird und sonst nichts mit ihr angestellt wird, ist es in diesem
spezifischen Programm unnötig, jedesmal den Wert aus dem Speicher in ein
Register zu lesen und später wieder aus dem Register zurück in den
Speicher zu schreiben. Das kann man weglassen, ohne dass sich irgendwas
ändert, und genau nach solchen Situationen sucht der Optimizer.
Wenn du aber eine ISR hast, die außerhalb des regulären Programmablaufs
plötzlich auch zugreifen will, funktioniert das nicht. Und genau dann
braucht man volatile, um dem Compiler zu sagen, dass er die
Speicherzugriffe auf jeden Fall durchführen muss.
Klaus schrieb:> Dazu muß der Compiler aber alle Funktionen, die in den 2 Sekunden> aufgerufen werden, komplett kennen um sicher zu wissen, das a[i] nie> geändert wird.
Bei LTO kennt er alle Funktionen komplett.
Klaus schrieb:> Ich versuche mir gerade vorzustellen, wie z.B. ein> strlen() auf einem Chararray abläuft, wenn ein Teil> der Chars in Registern liegt.
Es steht dem Compiler frei, strlen() zu kennen und das Ergebnis
vorwegzunehmen oder dessen Berechnung auszurollen. Für Funktionen wie
memcpy oder memset ist das jedenfalls durchaus üblich.
Eine Möglichkeit, den Speicherinhalt zu "invalidieren" ist eine
Memory-Barrier; ein GCC-Feature.
GNU-C Beispiel:
1
chararray[10];
2
3
voidfunc(void)
4
{
5
array[1]=2;
6
array[1]=3;
7
__asm____volatile__("":::"memory");
8
array[1]=4;
9
array[1]=5;
10
}
Optimierend übersetzt mit avr-gcc:
1
func:
2
ldi r30,lo8(array)
3
ldi r31,hi8(array)
4
ldi r24,lo8(3)
5
std Z+1,r24
6
ldi r24,lo8(5)
7
std Z+1,r24
8
/* epilogue start */
9
ret
Hier werden nur die Werte 3 und 5 gespeichert. Die 3, weil eine
Memory-Barrier folgt und die 5, weil GCC nicht weiß, was nach func()
folgt. 2 und 4 werden nicht gespeichert, weil sie durch 3 bzw. 5
überschrieben werden.
Falls die Memory-Barrier nicht per Gießkanne erfolgen soll sondern nur
bzgl. array, kann dies per asm-Argument modelliert werden:
1
__asm____volatile__("":"+m"(array));
Genau genommen ist die Operation nicht volatile, allerdings will man
i.d.R. vermeiden, dass sie mit einer volatile Anweisung kommutiert.
(Analog will man i.d.R. auch, dass eine volatile-Anweisung wie ein NOP
nicht mit einem Speicherzugriff kommutiert. Dies ist der Grund, warum
Makros / Lib-Funktionen für NOP etc. ein memory-Clobber enthalten
sollten.)
Weiterhin ist die Frage zu beantworten, warum Code mit volatile array
schlechter optimiert als ohne volatile. Operationen direkt auf der
volatile-Variable sollte dann vermieden werden und stattdessen mit einer
lokalen Kopie gearbeitet werden falls möglich.
Beispiel:
1
charvolatilearray[10];
2
3
voidfunc_1(void)
4
{
5
array[0]++;
6
if(array[0]>10)
7
array[0]=0;
8
}
9
10
voidfunc_2(void)
11
{
12
chara=array[0]+1;
13
if(a>10)
14
a=0;
15
array[0]=a;
16
}
func_2 gibt nicht nur besseren Code: func_1 leidet an einem möglichen
Glitch: Eine ISR findet u.U. den Wert 11 in array[0] vor, was den Code
in func_1 weiter verkompliziert / verlangsamt, falls dies zu vermeiden
ist:
1
#include<util/atomic.h>
2
3
voidfunc_1(void)
4
{
5
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
6
{
7
array[0]++;
8
if(array[0]>10)
9
array[0]=0;
10
}
11
}
Zur Referenz hier noch das Compilat, wieder mit avr-gcc:
1
func_1:
2
in r25,__SREG__
3
/* #APP */
4
cli
5
/* #NOAPP */
6
lds r24,array
7
subi r24,lo8(-(1))
8
sts array,r24
9
lds r24,array
10
cpi r24,lo8(11)
11
brlt .L2
12
sts array,__zero_reg__
13
.L2:
14
out __SREG__,r25
15
ret
16
17
func_2:
18
lds r24,array
19
subi r24,lo8(-(1))
20
cpi r24,lo8(11)
21
brlt .L4
22
ldi r24,0
23
.L4:
24
sts array,r24
25
ret
func_1 ist nicht nur größer und langsamer, sondern hat auch eine größere
Interrupt-Latenz.
Obwohl eine Memory-Barrier eine Möglichkeit ist, würde ich selbst
volatile bzw. Atomics falls verfügbar vorziehen.
Rolf M. schrieb:>> Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim>> Verlassen speichern, also auch ein Interrupt.>> Mir wäre keine Regel bekannt, die sowas vorgibt.
Ich denke, dazu bedarf es keiner Regel. Eine Funktion muß aus jedem
Kontext gerufen werden können (auch rekursiv), sie weiß also nicht, ob
eine globale Variable gerade in einem Register ist. Und an ihrem Ende
muß sie damit rechnen, daß ihr gesamter Kontext (außer dem Returnwert)
vernichtet wird.
Johann L. schrieb:> und die 5, weil GCC nicht weiß, was nach func() folgt.
Es bleibt also nichts anderes übrig, als am Anfang zu lesen und am Ende
zu schreiben.
MfG Klaus
Klaus schrieb:> Eine Funktion muß aus jedem Kontext gerufen werden können> (auch rekursiv), sie weiß also nicht, ob eine globale> Variable gerade in einem Register ist.
So die Theorie. Wenn der Compiler weiß, dass eine Funktion niemals
so aufgerufen wird, dann muss er keinen Code generieren, der damit
umgehen kann. Er muss nichtmal die Funktion selbst generieren.
Klaus schrieb:>> Mir wäre keine Regel bekannt, die sowas vorgibt.> Ich denke, dazu bedarf es keiner Regel.
Wenn es keine Regel gibt, die ein bestimmtes Verhalten vorschreibt, dann
sollte man sich darauf nicht unbedingt verlassen. Du triffst Annahmen
über den Compiler, die zwar meistens zutreffen, es aber nicht müssen.
S. R. schrieb:> Wenn es keine Regel gibt, die ein bestimmtes Verhalten vorschreibt, dann> sollte man sich darauf nicht unbedingt verlassen. Du triffst Annahmen> über den Compiler, die zwar meistens zutreffen, es aber nicht müssen.
Die globale Regel heißt, der Compiler muß immmer korekten Code
liefern. Und die gilt immer, nicht nur meistens. Und nur für die, die
das nicht verstanden haben, wird das nicht extra nochmal mal
aufgeschrieben.
MfG Klaus
S. R. schrieb:> Klaus schrieb:>>> Mir wäre keine Regel bekannt, die sowas vorgibt.>> Ich denke, dazu bedarf es keiner Regel.>> Wenn es keine Regel gibt, die ein bestimmtes Verhalten vorschreibt, dann> sollte man sich darauf nicht unbedingt verlassen. Du triffst Annahmen> über den Compiler, die zwar meistens zutreffen, es aber nicht müssen.
Richtig, das ist der entscheidende Unterschied.
Man kann entweder dem Sprachstandard folgen, laut dem das volatile nötig
ist und es einfach hinschreiben.
Oder man kann tief in den Codegenerierungsprozess einsteigen und sich
überlegen, warum es höchstwahrscheinlich trotzdem funktioniert, auch
wenn man das laut Standard nötige volatile weglässt.
Mir erscheint Variante 1 sinnvoller.
Klaus schrieb:> Die globale Regel heißt, der Compiler muß immmer korekten Code> liefern.
Natürlich. Dabei darf er aber die Struktur des Programms
berücksichtigen. Er muss nicht damit rechnen, dass etwas passiert, das
gar nicht im Programm steht.