Forum: Mikrocontroller und Digitale Elektronik Variable volatile auch wenn ISR nur liest?


von Rolf Rolfus (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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?

von Rolf Rolfus (Gast)


Lesenswert?

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. :)

von Rolf Rolfus (Gast)


Lesenswert?

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!

von my2ct (Gast)


Lesenswert?

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"

von neuer PIC Freund (Gast)


Lesenswert?

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.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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

von c-hater (Gast)


Lesenswert?

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...

von Rolf Rolfus (Gast)


Lesenswert?

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...

von (prx) A. K. (prx)


Lesenswert?

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.

von Rolf Rolfus (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Mach (Gast)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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
static int a[2];
2
3
int main ()
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.

: Bearbeitet durch Moderator
von Klaus (Gast)


Lesenswert?

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

von Rolf Rolfus (Gast)


Lesenswert?

Okay vielen Dank! Dann wird volatile also auf jeden Fall gesetzt und ich 
versuche das Drumherum dafür zu optimieren.

von Nop (Gast)


Lesenswert?

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.

von Codix (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.
1
#define IVAR(x)         (*(volatile typeof(x)*)&(x))

von Rolf M. (rmagnus)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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?

von Bastler (Gast)


Lesenswert?

> 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.

von Peter D. (peda)


Lesenswert?

Bastler schrieb:
> Und genau da hat man ein Problem.

Lies Dir einfach nochmal meine beiden Beiträge durch, darin ist alles 
erklärt.

von (prx) A. K. (prx)


Lesenswert?

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. ;-)

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

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

von Jürgen S. (starblue) Benutzerseite


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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 um 
unbestimmte Zeit verzögern, so dass die ISR im Speicher noch die 
Altdaten sieht."

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von HildeK (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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]); }

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

Im Falle von gcc sind ziemlich viele Standardfunktionen bereits im 
Compiler selbst eingebaut. Siehe 
https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Eine Möglichkeit, den Speicherinhalt zu "invalidieren" ist eine 
Memory-Barrier; ein GCC-Feature.

GNU-C Beispiel:
1
char array[10];
2
3
void func (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
char volatile array[10];
2
3
void func_1 (void)
4
{
5
    array[0]++;
6
    if (array[0] > 10)
7
        array[0] = 0;
8
}
9
10
void func_2 (void)
11
{
12
    char a = 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
void func_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.

von Klaus (Gast)


Lesenswert?

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

von S. R. (svenska)


Lesenswert?

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.

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

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.