Forum: Compiler & IDEs "Memory Barrier" in C99


von Bruno V. (bruno_v)


Lesenswert?

Ich habe 2 Fragen zu folgendem (sinnlosen) Demonstrationscode für in 
**C99!**
1
int flag, counter;
2
3
interrupt foo(void)
4
{
5
    if(!flag) { /* challenge a: counter muss sicher for flag gesetzt werden */
6
        counter++;
7
        flag = 1;
8
    }
9
}
10
11
int main(void)
12
{
13
    for(;;) {
14
       if(flag) {/* challenge b: Verwendung von counter bevor flag gelöscht wird */
15
           (Verwendung von counter);
16
           flag = 0; 
17
       }
18
       doSomething();
19
    } 
20
}
 * wie stelle ich sicher, dass flag erst "nach" counter gesetzt wird?
   * "asm" dazwischen oder Funktionsaufruf?
   * (implizites) Sprachmittel?
   * oder ist bei beiden ein Lock/Unlock zwingend (z.B. DI/EI)
 * ist "volatile" für flag oder counter zwingend notwendig? 
("doSomething" außerhalb der Übersetzungseinheit)

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Mit `volatile` wirst Du sicher erreichen können, dass der Compiler 
Zugriffe auf die Variablen nicht in der Reihenfolge ändert. Wenn Deine 
Hardware zusätzlich memory barrier braucht, damit dies sicher gestellt 
ist, wird der Compiler diese sicher nicht einfügen.

von Andras H. (andras_h)


Lesenswert?

Volatile ist nicht genug hier. Das macht nur dem Compiler bewusst, dass 
die Variable auch außerhalb der Context geändert werden kann. Deswegen 
wird praktisch beim jeden lese zugriff auf die Variable noch einmal 
gelesen. Bzw schreiben wird auch sofort abgesetzt. Aber Vorsicht, der 
Compiler weiß nichts von dem Cache. Der kann immer noch Probleme machen. 
Und der Compiler weiss auch nicht wie der CPU aufgebaut ist, ob der 
intern nochmal schreibefehle speichern kann. Es ist oft so, dass der RAM 
ECC bits hat, und eine Granularität von 8,16 oder 32 bytes sogar. Das 
heisst, es lohnt sich nicht sofort einen Read Modify Write zu machen, um 
8 bits zu schreiben, weil der über den Bus gehen muss und letztendlich 
werden im RAM doch 8 bytes geändert. Manche CPUs sammeln hier 
schreibbefehle.

In Embedded bereicht hast du sehr oft
MSYNC() und ISYCH() makros im Compiler abstraktion header file.
Diese makros müssen auf den jeweiligen asm befehle gemappt werden. Je 
nach Architektur kann es anders heissen.

MSYNCH macht sicher dass alle Schreibaufträge die bisher ausgeführt 
worden sind auf den Bus geschickt werden. Manche CPUs haben einen 
DataStorage Unit was einige Schreibbefehle speichern kann (wegen 
granularität des RAMs). Das heisst, wenn man etwas schreibt, kann sein 
dass das screiben gar nicht sofort ausgeführt wird. Manche Kontroller 
setzten den nie ab....

ISYNC macht sicher, dass alle Befehle bis zum ISYNC komplett 
abgearbeitet werden. Sprich alles ist durch den Pipeline und ist 
ausgeführt.

Da ich paranoid bin, würde ich so machen:
ISYNC() <= stellt sicher dass alle Befehle davor durch den CPU 
abgearbeitet worden sind
MSYNC() <= stellt sicher dass alle Schreibaufträge auf den Bus gelandet 
sind
ISYNC() <= stellt sicher dass der msynch auch abgearbeitet ist. 
Vermutlich braucht man das nicht, aber schaden kann es ja nicht.
Am Besten noch darauf achten dass der Cache auch write trough ist, und 
beim lesen entweder über nichtgecachte Addresse gehen oder den Cache 
invalidieren.

von Daniel G. (denial)


Lesenswert?

Wenn das normale Variablen sind und der Code auf einem CPU Core läuft, 
reicht volatile. Wenn main und foo auf unterschiedlichen CPU Cores 
laufen können, brauchst du sowas wie atomic_thread_fence aus C11. Auf 
Architekturen wie x86 kompiliert atomic_thread_fence zu nichts. Hast du 
kein C11, musst du das äquivalent mit inline Assembly einfügen.

Sehe gerade C11 hat atomic_signal_fence für den Fall, den man auch mit 
volatile lösen könnte.

: Bearbeitet durch User
von Udo K. (udok)


Lesenswert?

Bruno V. schrieb:
> ist "volatile" für flag oder counter zwingend notwendig?

Volatile nützt dir da nichts.  Du brauchst sowas wie Semaphore statt dem 
"flag" oder EnterCriticalSections/LeaveCriticalSection (Windows) oder du 
muss die Interrupts im kritischen Code ausschalten (Mikrocontroller ohne 
OS).

von Daniel G. (denial)


Lesenswert?

Udo K. schrieb:
> Volatile nützt dir da nichts.  Du brauchst sowas wie Semaphore statt dem
> "flag" oder EnterCriticalSections/LeaveCriticalSection (Windows) oder du
> muss die Interrupts im kritischen Code ausschalten (Mikrocontroller ohne
> OS).

So wie ich den Beispielcode verstehe, geht es nicht darum zu verhindern, 
dass foo aufgerufen wird bis flag den richtigen Wert hat, sondern darum 
zu verhindern, dass foo counter benutzt, wenn flag != 0. foo darf ruhig 
aufgerufen werden und nichts machen.

von Oliver S. (oliverso)


Lesenswert?

Torsten R. schrieb:
> Mit `volatile` wirst Du sicher erreichen können, dass der Compiler
> Zugriffe auf die Variablen nicht in der Reihenfolge ändert.

volatile ist nur und ausschließlich dafür gedacht, Speicherstellen mit 
Seiteneffekten (Hardwareregister o.ä.) dem Compiler nahezubringen.

Anwendungen von volatile für irgend etwas anderes, insbesondere zur 
Synchronisation von irgendwas, sind prinzipiell grundlegend falsch.

Wenn die Variablen im Codes des TO keine Seiteneffekte haben (keine 
Haedwareregister, kein Multithreading/-tasking), muß man sowieso die 
Anforderung bezgl. der zwingenden Reihenfolge der Operationen 
hinterfragen. Egal, wie der Compiler die Zugriffe auch umordnet, im 
Programm wird sich das nicht bemerkbar machen.

Ansonsten:
https://stackoverflow.com/questions/19965076/gcc-memory-barrier-sync-synchronize-vs-asm-volatile-memory

Oliver

von Udo K. (udok)


Lesenswert?

Daniel G. schrieb:
> foo darf ruhig
> aufgerufen werden und nichts machen.

Klar darf foo aufgerufen werden.  Der kritische Bereich ist ja die 
Abfrage ob flag gesetzt ist. Davor gehört ein EnterCriticalSection und 
danach ein LeaveCriticalSection oder aber er muss flag durch eine 
Semaphore Variable ersetzen.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Oliver S. schrieb:

> Anwendungen von volatile für irgend etwas anderes, insbesondere zur
> Synchronisation von irgendwas, sind prinzipiell grundlegend falsch.

Das ist so nicht ganz korrekt. Wenn Du (mit C99) keine anderes Mittel 
hast, dann musst Du Mittel verwenden, die a) funktionieren, b) aber 
nicht von der Sprache garantiert sind.

Du must an der Stelle einfach unterscheiden, welche Garantien Du von 
welcher Ebene bekommst. Interrupts gibt es ja auch nicht erst seit dem 
Jahr 2000. Und es hat schon vor 2000 zuverlässig funktioniert, dann aber 
eben nicht mit Mitteln der Sprache (C99). Dazu gab es schon immer 
Erweiterungen im Compiler, die Dir die benötigten Garantien geben (und 
das kann sehr wohl in vielen Fällen einfach ein `volatile` sein) z.b. 
irgendwelche Intrinsicts. Üblicherweise gibt es für die Kombination aus 
Hardware und Compiler eine Dokumentation, die beschreibt, was zu tun 
ist, damit man die gewünschten Garantien bekommt.

Neuere Varianten von C und C++ haben Möglichkeiten, die gewünschten 
Garantien einzufordern.Aber der OP hat ja explizit nach C99 gefragt.

von Oliver S. (oliverso)


Lesenswert?

Torsten R. schrieb:
> Das ist so nicht ganz korrekt. Wenn Du (mit C99) keine anderes Mittel
> hast, dann musst Du Mittel verwenden, die a) funktionieren, b) aber
> nicht von der Sprache garantiert sind.

Was einen natürlich auf sehr dünnes Eis bringen kann. Wenn’s irgendwo 
dokumentiert ist, daß der Compiler aus praktischer Vorsicht 
volatile-Zugriffe nicht umordnet, ok, aber nur „funktioniert halt“ ist 
schwierig.

Oliver

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Oliver S. schrieb:

> Was einen natürlich auf sehr dünnes Eis bringen kann. Wenn’s irgendwo
> dokumentiert ist, daß der Compiler aus praktischer Vorsicht
> volatile-Zugriffe nicht umordnet, ok, aber nur „funktioniert halt“ ist
> schwierig.

Es ist aber das einzige Eis, dass es vor 2000 gab. Und auch "früher" 
wurde schon funktionierende Software in C geschrieben.

Es geht um die Kombination aus Compiler und Hardware. Und es geht nicht 
darum, dass es irgendwie funktioniert, sondern dass der (in der Regel) 
Hardware-Hersteller garantiert, dass bestimmte Hochsprachenkonstrukte 
mit bestimmten Compilern funktionieren. Vor 2000 wurde ja auch nicht 
alles an Firmware in Assembler geschrieben.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Oliver S. schrieb:

> volatile ist nur und ausschließlich dafür gedacht, Speicherstellen mit
> Seiteneffekten (Hardwareregister o.ä.) dem Compiler nahezubringen.

Nein. volatile ist dafür gedacht, dem Compiler nahezubringen, dass der 
Inhalt einer Speicherstelle sich durch ein Ereignis ändern könnte, was 
unabhängig vom aktuell durch den Compiler behandelten Code geschieht.

Das kann natürlich eine hardware-induzierte Änderung in 
Hardwareregistern sein. Aber nicht nur. Eine Änderung durch eine ISR 
betrifft das ganz genau so. Denn auch die passiert außerhalb des 
aktuellen Compiler-Scopes. Und natürlich auch eine Änderung durch 
konkurrierende Cores.

Was du meinst, sind zusätzliche Rücksichten, die man nehmen muss. Also 
Sachen, zu deren Behandlung volatile alleine nicht genügt.
Das sind dann im einfachsten Fall Sachen, die sich mit atomics 
erschlagen lassen. Das ist sozusagen die nächste Eskalationsstufe. 
Darüber kommen dann noch die Sachen, die aus der Existenz von Caches 
oder Pipelines resultieren. Dafür braucht es dann weitere Maßnahmen.

Was du offensichtlich nicht verstanden hast: all die höheren 
Eskalationen implizieren die jeweils geringeren. Nur weil die sie nicht 
explizit hinschreiben mußt, heißt das nicht, dass sie nicht implizit 
Teil der höheren Konstrukte wären.

Du bist ein typischen Hochsprachen-Programmierer. Du verstehst nur die 
Form, nicht wirklich die Funktion der von dir verwendeten Konstrukte.

von Bruno V. (bruno_v)


Lesenswert?

Vielen Dank, für die Antworten bisher. Ein wenig Kontext:

Der Beispielcode würde so OK sein, wenn die Abfrage in der Reihenfolge 
erfolgt. Beide "Threads" spielen dann Ping-Pong mit "flag" als binärem 
Token. Ob foo "umsonst" aufgerufen wird (mit flag noch immer gesetzt) 
spielt keine Rolle.

Alles auf einem Prozessor, so dass im Zweifel volatile und DI/EI 
reichen. Wüsste nur gerne, ob es notwendig ist.

Ich verstehe volatile mit dem Link von Oliver so: wenn, müssen beide 
Variablen volatile sein, da der Compiler "non volatile" Zugriffe 
"drumherum" verschieben darf. Beispiel: Wenn nur flag volatile ist, kann 
der Compiler es nach der Abfrage sofort setzen und counter danach 
behandeln.

Nehmen wir an, flag und counter sind volatile. Reicht das? Bei jedem 
Durchlauf (in beiden Funktionen) wird dann
 A) flag abgefragt
 B) counter nur bearbeitet, wenn flag "richtig" war
 C) flag erst am Ende umgestellt

Darf der Prozessor den Code noch umsortieren?

von Daniel G. (denial)


Lesenswert?

Nein, mit volatile darf der Compiler die Zugriffe nicht umsortieren.

Das volatile schützt dich aber auch davor, dass der Compiler Annahmen 
dazu macht, welchen Wert die Variablen haben. Wenn der Compiler weiß, 
dass doSomething flag nicht ändert, würde er vermutlich den if(flag) 
Block vor die Schleife ziehen. Dass foo als Interrupt-Handler zu einem 
beliebigen Zeitpunkt aufgerufen werden kann, weiß er nicht.

von Daniel G. (denial)


Lesenswert?

Udo K. schrieb:
> Der kritische Bereich ist ja die Abfrage ob flag gesetzt ist.

Es ist schwierig sich eine CPU-Architektur vorzustellen, auf der das 
Ändern einer Variable von 0 nach 1 oder von 1 nach 0 nicht atomar 
passiert.

von Udo K. (udok)


Lesenswert?

Bruno V. schrieb:
> Nehmen wir an, flag und counter sind volatile. Reicht das? Bei jedem
> Durchlauf (in beiden Funktionen) wird dann
>  A) flag abgefragt
>  B) counter nur bearbeitet, wenn flag "richtig" war
>  C) flag erst am Ende umgestellt

Glaube nicht das volatile da was nützt.

Die Abfrage von flag passiert ja bevor flag invertiert wird.
Da würde ein volatile nur bewirken das flag neu aus dem RAM gelesen wird 
und eventuelles Vorwissen des Compilers über den Wert von flag verworfen 
wird.
Der Compiler hat aber zu dem Zeitpunkt gar kein Vorwissen, da flag das 
erste Mal in der Funktion eingelesen wird.

Für das Setzen von flag macht volatile auch keinen Unterschied.
Auch wenn flag volatile ist, kann der Compiler den Code umordnen.

was aber einen Unterschied macht ist das flag eine globale Variable ist.
Wenn du also anstatt "counter++" in Zeile 6 eine Funktion 
IncrementCounter() aufrufst, die in einem anderen Modul definiert ist, 
dann darf der Compiler flag erst nach dem Funktionsaufruf setzen, da er 
annehmen muss dass IncrementCounter() den Wert der globalen Variable 
flag verwendet.
Das wäre eine saubere Lösung, die mit allen C Versionen bis K&R 
funktioniert.

von Udo K. (udok)


Lesenswert?

Daniel G. schrieb:
> Udo K. schrieb:
>> Der kritische Bereich ist ja die Abfrage ob flag gesetzt ist.
>
> Es ist schwierig sich eine CPU-Architektur vorzustellen, auf der das
> Ändern einer Variable von 0 nach 1 oder von 1 nach 0 nicht atomar
> passiert.

Du hast das Problem nicht verstanden.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Daniel G. schrieb:
> Udo K. schrieb:
>> Der kritische Bereich ist ja die Abfrage ob flag gesetzt ist.
>
> Es ist schwierig sich eine CPU-Architektur vorzustellen, auf der das
> Ändern einer Variable von 0 nach 1 oder von 1 nach 0 nicht atomar
> passiert.

Nö, nicht wirklich. Das ist auf AVR8 z.B. keine Problem. Also vielmehr: 
kann durchaus ein Problem sein. Nämlich dann wenn die Variable ein 
Integertyp mit mehr als einem Byte Breite ist.

Ja OK. Wenn es nur um die Werte 0 und 1 geht, ist das kein Problem. Aber 
wenn es z.B. auch um den Wechsel zwischen 0 und -1 (oder irgendwas 
anderes, was einfach nur nicht null ist, also z.B. bool-true) geht, dann 
schon...

von Daniel G. (denial)


Lesenswert?

Udo K. schrieb:
> Du hast das Problem nicht verstanden.

Dann erleuchte mich. Zeige mir, wo in dem obigen Beispiel ohne 
EnterCriticalSection/LeaveCriticalSection o.ä. ein Zugriff aus einem 
Interrupt oder anderem Thread dazwischenfunken kann und etwas 
ungewolltes passiert. Wir können gerne auf Assemblerebene runtergehen.

: Bearbeitet durch User
von Bruno V. (bruno_v)


Lesenswert?

Daniel G. schrieb:
> Wenn der Compiler weiß,
> dass doSomething flag nicht ändert, würde er vermutlich den if(flag)
> Block vor die Schleife ziehen.

ja, deshalb

Bruno V. schrieb:
> ("doSomething" außerhalb der Übersetzungseinheit)

Ich hätte interrupt foo1(void) und interrupt foo2(void) nehmen sollen. 
Auch, weil dann nicht klar ist, welches foo das andere unterbrechen 
kann.

Daniel G. schrieb:
> wo in dem obigen Beispiel ohne
> EnterCriticalSection/LeaveCriticalSection o.ä. ein Zugriff aus einem
> Interrupt oder anderem Thread dazwischenfunken kann und etwas
> ungewolltes passiert.

ohne volatile für beide (und ohne Funktionsaufruf dazwischen): wenn flag 
direkt nach der Abfrage gelöscht wird, weil der Compiler die Zuweisungen 
neu ordnet.

Udo K. schrieb:
> anstatt "counter++" in Zeile 6 eine Funktion IncrementCounter()...
> Das wäre eine saubere Lösung, die mit allen C Versionen bis K&R
> funktioniert.
Wimre habe wir stattdessen früher ein "Assembler NOP" in C eingefügt. 
Die genauen Zusicherungen dazu kenne ich nicht mehr.
Darf mit so einem Nop nach counter++ nicht sogar das volatile entfallen?

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Ob S. schrieb:
> Das kann natürlich eine hardware-induzierte Änderung in
> Hardwareregistern sein. Aber nicht nur. Eine Änderung durch eine ISR
> betrifft das ganz genau so. Denn auch die passiert außerhalb des
> aktuellen Compiler-Scopes. Und natürlich auch eine Änderung durch
> konkurrierende Cores.

Genau diese Meinung hat dazu geführt, daß C++ volatile wegen der 
Vielzahl falscher Benutzung mehr oder weniger ganz aus der Sprache 
streichen wollte. Das haben die zum Glück dann nicht gemacht, aber nein, 
außer den Hardware-induzierten Änderungen gibt es keinen Anwendungsfall 
für volatile.

Oliver

von Oliver S. (oliverso)


Lesenswert?

Bruno V. schrieb:
> Wimre habe wir stattdessen früher ein "Assembler NOP" in C eingefügt.
> Die genauen Zusicherungen dazu kenne ich nicht mehr.
> Darf mit so einem Nop nach counter++ nicht sogar das volatile entfallen?

Das NOP ist dem Compiler völlig egal. Das entscheidende ist, daß dieses 
Assembler-NOP auch eine memory barrier eingefügt hat. Das NOP kann man 
dann in der Assembleranweisung auch ganz weglassen.

Oliver S. schrieb:
> Ansonsten:
> 
https://stackoverflow.com/questions/19965076/gcc-memory-barrier-sync-synchronize-vs-asm-volatile-memory

Und weils so schön ist, hier noch die allumfassende Weisheit zum Thema:

https://www.kernel.org/doc/html/v6.6/core-api/wrappers/memory-barriers.html#

Oliver

: Bearbeitet durch User
von Nemopuk (nemopuk)


Lesenswert?

Daniel G. schrieb:
> Wenn das normale Variablen sind und der Code auf einem CPU Core läuft,
> reicht volatile.

Das ist nicht richtig.

Bei 8 Bit AVR können Zugriffe auf die beiden Bytes des 16 Bit Counter 
unterbrochen werden. Um dies zu verhindern kann man ggf. interrupts 
temporär blockieren. Da C99 dafür keinen Befehl hat, weicht man auf 
hardwarespezifische Befehle aus, hier cli() und sei(). Und ja, dafür 
gibt es im avr-gcc auch Makros (irgendwas mit "atomic" im Namen).

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Ich bin ja schon eine ziemliche Weile aus dem Thema raus. Gibt es in 
irgendwelchen neueren C und C++ Standards keine offiziellen Mechanismen, 
die über das aus einem anderen Zeitalter stammende "volatile" 
hinausgehen, und beispielsweise atomar zu behandelnde Daten betreffen?

: Bearbeitet durch User
von Daniel G. (denial)


Lesenswert?

Nemopuk schrieb:
> Bei 8 Bit AVR können Zugriffe auf die beiden Bytes des 16 Bit Counter
> unterbrochen werden.

Um dies zu verhindern, hat das Beispiel die flag Variable, die sich, wie 
oben bereits erwähnt, effektiv immer nur in einem Bit, also atomar, 
ändert.

Bruno V. schrieb:
> ("doSomething" außerhalb der Übersetzungseinheit)

Sich darauf zu verlassen, dass der Compiler deshalb nicht weiß, ob 
doSomething flag verändert, geht nur so lange gut, bis jemand flag 
static macht oder Link-Time-Optimization einschaltet.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

PS: Hier ist zwar explizit nach C99 gefragt, aber einen etwas grösseren 
Scope kann man dem Thema m.E. einräumen. Ist ja abgesehen von der 
Version des Standards eine allgemein gehaltene Frage, ohne Bezug auf 
irgendeine Plattform, weder Compiler noch Hardware. Bei C99 entsteht das 
Problem, dass man nicht völlig plattformunabhängig antworten kann.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Bruno V. schrieb:

> Beide "Threads" spielen dann Ping-Pong mit "flag" als binärem
> Token. O

Threads sind ein anderes Thema. Du hast eine Threading library (z.B. 
pthread) und die legt fest, was zu tun ist, damit eine Änderung eines 
threads für einen anderen thread sichtbar wird.

von Falk B. (falk)


Lesenswert?

Daniel G. schrieb:
> Nemopuk schrieb:
>> Bei 8 Bit AVR können Zugriffe auf die beiden Bytes des 16 Bit Counter
>> unterbrochen werden.
>
> Um dies zu verhindern, hat das Beispiel die flag Variable, die sich, wie
> oben bereits erwähnt, effektiv immer nur in einem Bit, also atomar,
> ändert.

Nein. Es geht um Hardwareregister von Timern etc. Davon gibt es mehrere. 
Wenn nun ein Zugriff in Hauptprogramm und in der ISR auf solche Register 
erfolgt, kann es krachen. Hier braucht man atomaren Zugriff. Ähnliches 
gilt für normale 8 Bot Portzugriffe, wenn die nichtatomare 
Read-Modify-Write Zugriffe machen. Siehe

https://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff
1
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
2
        tmp = TNCT1;
3
    }

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

(prx) A. K. schrieb:
> Ich bin ja schon eine ziemliche Weile aus dem Thema raus. Gibt es
> in
> irgendwelchen neueren C und C++ Standards keine offiziellen Mechanismen,
> die über das aus einem anderen Zeitalter stammende "volatile"
> hinausgehen, und beispielsweise atomar zu behandelnde Daten betreffen?

C:
https://en.cppreference.com/w/c/atomic/memory_order.html
https://en.cppreference.com/w/c/language/atomic.html

C++ vergleichbar…

Oliver

von Bruno V. (bruno_v)


Lesenswert?

Oliver S. schrieb:
> Ob S. schrieb:
>> ... hardware-induzierte Änderung in Hardwareregistern
>>
>> ... Eine Änderung durch eine ISR betrifft das ganz genau so.
>
> aber nein, außer den Hardware-induzierten Änderungen
> gibt es keinen Anwendungsfall
> für volatile.

Konkrete Frage: subsummierst Du eine Variable, die in einem Interrupt 
geändert wird, auch als "HW-induziert"? Ohne volatile könnte der 
Compiler while(!f) doch zur Endlosschleife machen, oder?:
1
volatile char f;
2
interrupt foo(void) {f = 1;}
3
4
void foo2(void) {
5
   while(!f){} /* warte auf foo */
6
   ...
7
}

: Bearbeitet durch User
von Daniel G. (denial)


Lesenswert?

Falk B. schrieb:
> Nein. Es geht um Hardwareregister von Timern etc. Davon gibt es mehrere.
> Wenn nun ein Zugriff in Hauptprogramm und in der ISR auf solche Register
> erfolgt, kann es krachen.

Aber das obige Beispiel greift aus foo nicht auf Hardwareregister zu. 
counter ist eine normale int-Variable.

von Andreas M. (amesser)


Lesenswert?

Bruno V. schrieb:
> Ohne volatile könnte der
> Compiler while(!f) doch zur Endlosschleife machen, oder?:

Das kommt darauf an, was in dem while steht. Wenn da nämlich 
Funktionsaufrufe drinnen stehen deren Inhalt der Compiler nicht kennt, 
könnte es ja sein, das diese "f" verändern. Dann (f ist hier nicht 
static...)

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Bruno V. schrieb:
> Ohne volatile könnte der
> Compiler while(!f) doch zur Endlosschleife machen, oder?

Kann er nicht nur, darf er und macht er.

Er macht es nicht, wenn du die Variable volatile machst oder wenn du in 
die Schleife eine memory barrier einfügst, oder wenn du irgend eine 
dafür vorgesehene Möglichkeit deiner toolchain nutzt.
Das Problem Atomic löst volatile und auch eine memory barrier nicht, bei 
den anderen Varianten kommt es darauf an.

Da der Anwendungsfall aber bis auf „sinnlosen Code“ nicht weiter 
definiert ist, und auch die Einschränkung auf c99 ziemlich willkürlich 
erscheint, musst du halt die Doku deiner Toolchain und deiner Harware 
lesen, und klären, was dein Problem überhaupt ist.

https://en.cppreference.com/w/c/language/atomic.html

„The volatile types do not provide inter-thread synchronization, memory 
ordering, or atomicity.“

Oliver

: Bearbeitet durch User
von Bruno V. (bruno_v)


Lesenswert?

Oliver S. schrieb:
> Kann er nicht nur, darf er und macht er.
>
> Er macht es nicht, wenn du die Variable volatile machst

OK. Es ging um Deine Aussage hier

Oliver S. schrieb:
> Genau diese Meinung hat dazu geführt, daß C++ volatile wegen der
> Vielzahl falscher Benutzung mehr oder weniger ganz aus der Sprache
> streichen wollte. Das haben die zum Glück dann nicht gemacht, aber nein,
> außer den Hardware-induzierten Änderungen gibt es keinen Anwendungsfall
> für volatile.

Ich hoffe, ich verstehe Dich richtig, dass mein Beispiel eine weitere, 
legitime Anwendung (atomarer Zugriff vorausgesetzt) von volatile ist 
ODER Du Interrupts generell als "HW induziert" siehst. Beides ist OK für 
mich. Danke.

(Es handelt sich übrigens um einen NXP e200z7, 266MHz mit Windriver 
Compiler. Daher war ich entsprechend überrascht. Mein Part hat mit dem 
parallel vorhandenen Safety-Doppelkern nix zu tun.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Bruno V. schrieb:
> Windriver

ist doch nicht nur der Compiler, da wird doch das zugehörige RTOS im 
Spiel sein. Da würde ich mal in die Doku schauen, was das so für die 
Aufgabenstellung im Angebot hat.

Oliver

von Daniel G. (denial)


Lesenswert?

Oliver S. schrieb:
> „The volatile types do not provide inter-thread synchronization, memory
> ordering, or atomicity.“

Volatile garantiert es nicht, aber die Variablen können diese 
Eigenschaften trotzdem oder zumindest in beschränktem Umfang haben. Z.B. 
ist memory ordering selbst auf ARM gegeben, wenn alle Zugriffe vom 
gleichen CPU Core aus geschehen. Und atomicity ist für einfaches lesen 
und setzen (einzeln, nicht in kombination) zumindest für einzelne Bytes 
immer gegeben.

von Udo K. (udok)


Lesenswert?

Volatile hat nichts mit Hardware Register oder mit Interrupts oder mit 
dem Cache zu tun.

Volatile sagt dem Compiler nur, dass er bei jeder Verwendung einer 
Variable diese neu aus dem RAM lesen (schreiben) muss.
Ohne volatile könnte der Compiler die Variable in einem Register 
zwischenspeichern oder sie nur einmal am Ende eines Blocks schreiben.

Für den Code in der main Schleife (Zeile 14) ist volatile flag nicht 
notwendig, da die externe Funktion doSomething() die flag Variable ja 
ändern könnte, und der Compiler daher maximal das if(flag){flag=0} zu 
flag=0 verändern darf.

Volatile garantiert aber nicht dass if(!flag){counter++;flag=1;} (Zeile 
6,7) so ausgeführt wird.  Der Compiler könnte den Code zu if(!flag){ 
flag=1;counter++;} ändern.  Wenn man das sicher verhindern will, dann 
kann man eine externe Nop() Funktion nach counter++ aufrufen.

von (prx) A. K. (prx)


Lesenswert?

Udo K. schrieb:
> da die externe Funktion doSomething() die flag Variable ja
> ändern könnte

Wenn der Compiler die Übersetzungseinheiten nur vorkompiliert und in der 
eigentlichen Stufe alle zusammen betrachtet, kann das ins Auge gehen.

: Bearbeitet durch User
von Daniel G. (denial)


Lesenswert?

Udo K. schrieb:
> Volatile garantiert aber nicht dass if(!flag){counter++;flag=1;} (Zeile
> 6,7) so ausgeführt wird.  Der Compiler könnte den Code zu if(!flag){
> flag=1;counter++;} ändern.

Nein, volatile garantiert, dass es nicht umsortiert wird. Der N1256 C99 
Draft sagt zu volatile (u.a.):

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

counter++; und flag=1; sind Expression Statements und haben somit nach 
Annex C einen Sequence Point zwischen sich.

von Bruno V. (bruno_v)


Lesenswert?

Udo K. schrieb:
> Volatile garantiert aber nicht dass if(!flag){counter++;flag=1;} (Zeile
> 6,7) so ausgeführt wird.  Der Compiler könnte den Code zu if(!flag){
> flag=1;counter++;} ändern.

In Ergänzung zu Daniel: Wenn nur eine Variable volatile ist, ja. Wenn 
beide volatile sind: nein. Zumindest, wenn der Compiler dafür sorgt, 
dass der Prozessor später nicht noch umsortiert.

von Udo K. (udok)


Lesenswert?

Daniel G. schrieb:
> "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."

Das hast du nicht richtig verstanden.

Das gilt nur für die abstrakte C-Standard-Maschine.  Wenn der Compiler 
garantieren kann, dass die Variable am Ende der Funktion richtig im 
Speicher steht, muss er sie nicht 3x abspeichern blos weil irgendwo 
flag=1;flag=2;flag=3; steht.

von Udo K. (udok)


Lesenswert?

(prx) A. K. schrieb:
> Wenn der Compiler die Übersetzungseinheiten nur vorkompiliert und in der
> eigentlichen Stufe alle zusammen betrachtet, kann das ins Auge gehen.

Ja das geht ins Auge.  Ist in C nicht notwendig und führt in die 
Debugging Hölle.

von Harald K. (kirnbichler)


Lesenswert?

Falk B. schrieb:
> Es geht um Hardwareregister von Timern etc. Davon gibt es mehrere.
> Wenn nun ein Zugriff in Hauptprogramm und in der ISR auf solche Register
> erfolgt, kann es krachen.

Das kann es nur, wenn die Hardwareregister breiter sind als die native 
Busbreite des µC - wenn das also z.B. ein 16-Bit-Register ist, und das 
auf einem 8-Bit-AVR angesteuert werden soll.

Darin unterscheidet sich das Register in keiner Weise von einer 
Variablen, die größer ist als die native Busbreite; ein Zugriff auf 
einen 16-Bit-Int kann also genau dasselbe Problem hervorrufen.

Ist aber das Register nicht größer, ist der Zugriff sowieso atomar.

von Bruno V. (bruno_v)


Lesenswert?

Udo K. schrieb:
> Wenn der Compiler
> garantieren kann, dass die Variable am Ende der Funktion richtig im
> Speicher steht, muss er sie nicht 3x abspeichern blos weil irgendwo
> flag=1;flag=2;flag=3; steht.

Wenn flag volatile ist, muss er jeden Schreibzugriff machen.

von Oliver S. (oliverso)


Lesenswert?

Udo K. schrieb:
> (prx) A. K. schrieb:
>> Wenn der Compiler die Übersetzungseinheiten nur vorkompiliert und in der
>> eigentlichen Stufe alle zusammen betrachtet, kann das ins Auge gehen.
>
> Ja das geht ins Auge.  Ist in C nicht notwendig und führt in die
> Debugging Hölle.

Das ist in C genauso erforderlich wie in allen anderen 
Programmiersprachen auch.

Oliver

von Falk B. (falk)


Lesenswert?

Harald K. schrieb:
>> Es geht um Hardwareregister von Timern etc. Davon gibt es mehrere.
>> Wenn nun ein Zugriff in Hauptprogramm und in der ISR auf solche Register
>> erfolgt, kann es krachen.
>
> Das kann es nur, wenn die Hardwareregister breiter sind als die native
> Busbreite des µC - wenn das also z.B. ein 16-Bit-Register ist, und das
> auf einem 8-Bit-AVR angesteuert werden soll.

Das meinte ich ja, stand auch als Zitat drüber.

> Darin unterscheidet sich das Register in keiner Weise von einer
> Variablen, die größer ist als die native Busbreite; ein Zugriff auf
> einen 16-Bit-Int kann also genau dasselbe Problem hervorrufen.

Jain. Bei den Hardwareregistern ist es ein unsichtbares, temporäres 
Register zum atomaren Lesen/Schreiben in Richtung Hardware, was aber 
bezüglich Software und ISRs eben NICHT atomar wirkt!

> Ist aber das Register nicht größer, ist der Zugriff sowieso atomar.

Nö. Ein Read Modify Write Zugriff, welcher eben NICHT sbi/cbi nutzt, um 
einzelne Bits zu ändern, ist es auch nicht! Nur komplette, einfache 
Bytezugriffe beim Lesen oder Schreiben!

von Udo K. (udok)


Lesenswert?

Udo K. schrieb:
> Daniel G. schrieb:
>> "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."
>
> Das hast du nicht richtig verstanden.

Ich hatte dich falsch zitiert und das volatile vor dem Zitat übersehen.

Wenn die Variable volatile ist, dann muss der Compiler die Reihenfolge 
einhalten.  Die Reihenfolge in counter++;flag=1; ist also sicher wenn 
beide Variablen volatile sind.

von Udo K. (udok)


Lesenswert?

Oliver S. schrieb:
>> Ja das geht ins Auge.  Ist in C nicht notwendig und führt in die
>> Debugging Hölle.
>
> Das ist in C genauso erforderlich wie in allen anderen
> Programmiersprachen auch.

Link-Time Optimierung bringt in normalem C fast nichts.  Das kommt aus 
der C++ Welt, wo es hunderte winzige Getter und Setter Funktionen gibt 
und viele verschachtelte Klassen, die wenig machen.  Dazu die Standard 
Template Libs.  Alles Inline zu deklarieren geht auch schlecht weil der 
Code dann nicht mehr lesbar ist und der Modul Gedanke sinnlos wird. 
Anwender einer Lib sollen ja nicht in die Funktionen reinschauen.

Aber versuche mal so ein Programm von dem du nur einen Core-Dump vom 
Kunden und (hoffentlich) ein Symbolfile hast zu debuggen.  Wenn 
Funktionen teils geinlined werden, teils mit anderen Funktionen mit 
identischem Asm-Code zusamengelegt werden, wo unterschiedliche 
Funktionsaufrufe unterschiedlich optimiert werden.  Das ist die 
Debugging Hölle.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Daniel G. schrieb:
> Z.B. ist memory ordering selbst auf ARM gegeben, wenn alle Zugriffe vom
> gleichen CPU Core aus geschehen.

Ich denke nicht. In den Load-Store-Units der Applikationsprozessoren 
findest du mehrere Slots, die nach bestimmten Regeln umsortiert werden 
dürfen (siehe Architecture Reference Manual).
Selbst auf Cortex-M Prozessoren ließen sich dank der Harvard-Architektur 
noch Szenarien konstruieren, die sich in diesem Zusammenhang kritisch 
verhalten könnten.

von Daniel G. (denial)


Lesenswert?

Marcus H. schrieb:
> Ich denke nicht. In den Load-Store-Units der Applikationsprozessoren
> findest du mehrere Slots, die nach bestimmten Regeln umsortiert werden
> dürfen (siehe Architecture Reference Manual).

Ich würde sogar noch weiter gehen und behaupten, dass auf ARM ein Load 
von normal RAM genau das sieht, was der letzte Store in Program Order 
davor an die Adresse geschrieben hat. Schweinereien mit der Page Table 
und dem Cache zwischen Store und Load mal außen vor gelassen.

von (prx) A. K. (prx)


Lesenswert?

Es kann davon abhängen, ob Adresse und Breite der Zugriffe identisch 
sind, oder sich lediglich überlappen.

Es kann davon abhängen, ob man sich auf die Sicht eines einzelnen 
Cores/Threads beschränkt, ob auch die Sicht eines zweiten auf die 
Zugriffe des ersten eine Rolle spielt, oder die Sicht des RAMs gemeint 
ist (DMA).

: Bearbeitet durch User
von Bruno V. (bruno_v)


Lesenswert?

(prx) A. K. schrieb:
> ob man sich auf die Sicht eines einzelnen
> Cores/Threads beschränkt, ob auch die Sicht eines zweiten auf die
> Zugriffe des ersten eine Rolle spielt, oder die Sicht des RAMs gemeint
> ist (DMA).

Mein Prozessor hat zwar 2 Cores (einer sogar doppelt), aber mich 
interessiert nur 1 Core. Ich habe "Threads" oben geschrieben, meinte 
damit aber
 * 2 konkurrierende Kontexte
 * egal ob Interrupts oder Tasks des OS
 * wobei beide "Seiten" von je genau einem Kontext aufgerufen werden, 
also auch nicht gegen sich selbst gemutexed oder was auch immer werden 
müssen.

von Andreas M. (amesser)


Lesenswert?

Daniel G. schrieb:
> Ich würde sogar noch weiter gehen und behaupten, dass auf ARM ein Load
> von normal RAM genau das sieht, was der letzte Store in Program Order
> davor an die Adresse geschrieben hat. Schweinereien mit der Page Table
> und dem Cache zwischen Store und Load mal außen vor gelassen.

Ja natürlich. Alles andere wäre auch ganz großer Murks, wie sollte man 
so ein Teil sonst programmieren, wenn man nach dem Schreiben nicht das 
gleiche wieder zurücklesen würde. (Es geht hier erstmal gar nicht darum 
ob die Daten auch wirklich im RAM angekommen sind oder nicht, Die CPU 
hat auch interne Buffer wo Writes drinnen landen können)

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Marcus H. schrieb:
>> Daniel G. schrieb:
>> Z.B. ist memory ordering selbst auf ARM gegeben, wenn alle Zugriffe vom
>> gleichen CPU Core aus geschehen.
>
> Ich denke nicht. In den Load-Store-Units der Applikationsprozessoren
> findest du mehrere Slots, die nach bestimmten Regeln umsortiert werden
> dürfen

Daniel G. schrieb:
> Ich würde sogar noch weiter gehen und behaupten, dass auf ARM ein Load
> von normal RAM genau das sieht, was der letzte Store in Program Order
> davor an die Adresse geschrieben hat.

Ja, natürlich. Mir ging es auch eher auf diesen Punkt im Ausgangspost 
des Threads:
«wie stelle ich sicher, dass flag erst "nach" counter gesetzt wird?»

Das sind zwei unterschiedliche Adressen, deren Zugriffsreihenfolge in 
der Hardware nicht bei jeder Zielarchitektur garantiert ist. Mit 
Bordmitteln der Sprache C geht das jedenfalls nicht zuverlässig.

Beitrag #8014191 wurde vom Autor gelöscht.
von Andreas M. (amesser)


Lesenswert?

Marcus H. schrieb:
> as sind zwei unterschiedliche Adressen, deren Zugriffsreihenfolge in
> der Hardware nicht bei jeder Zielarchitektur garantiert ist. Mit
> Bordmitteln der Sprache C geht das jedenfalls nicht zuverlässig.

Das hat mit C gar nix zu tun. Der interne RAM ist bei ARM z.B. 
üblicherweise als "Normal" in der MPU/MMU getaggt. D.h. am Memory Port 
der CPU kommen die Schreibzugriffe in irgend einer Reihenfolge raus. Es 
kann sogar sein, das die gar nicht rauskommen wenn sie zwischenzeitlich 
obsolet sind (Write Back Cache z.B.) Und es wird noch verrückter, der 
Speichercontroller darf die Zugriffe auch nochmal umsortieren so wie es 
ihm am besten passt.

Es spielt aber gar keine Rolle, was außen passiert. Wenn im 
Assembler/Maschinencode  zunächst "Schreibe Adresse A", danach optional 
"Schreibe Adresse B" und schließlich "Lese A (oder B)" kommt dann wird 
der Lesezugriff genau den jeweils vorher geschriebenen Wert 
zurückliefern. Und zwar völlig unabhängig davon ob der Wert überhaupt im 
RAM angekommen ist, noch im Cache oder im Writebuffer rumschimmelt oder 
der Schreibzugriff im User Kontext und der Lesezugriffe aus dem 
Interruptkontext erfolgt oder umgekehrt oder ob der Speichercontroller 
intern umsortiert hat oder nicht.

Alles andere wäre komplett sinnfrei, eine CPU die nicht das tut was man 
ihr sagt oder erst mit einer undefinierten Verzögerung ist nutzlos, man 
müsste überall memory barriers einfügen.

"volatile" bezieht sich nur auf den C-Compiler und bewirkt (u.a.) das 
Zugriffe auf zwei volatile Variablen im generierten 
Assembler/Maschinencode nicht vertauscht werden dürfen und diese auch 
nicht wegoptimiert werden dürfen.

von Oliver S. (oliverso)


Lesenswert?

Andreas M. schrieb:
> Das hat mit C gar nix zu tun.

Doch, hat es. Wenn man in C programmiert, hat man halt nur die Mittel 
der Sprache zur Verfügung.


> Alles andere wäre komplett sinnfrei, eine CPU die nicht das tut was man
> ihr sagt oder erst mit einer undefinierten Verzögerung ist nutzlos, man
> müsste überall memory barriers einfügen.

Es ist nicht ganz klar, ob du das Problem überhaupt verstanden hast. 
Erstens tut beim Programmieren in C eine CPU nie das, was man ihr sagt, 
sondern höchstens etwas, was für dich von außen so aussieht, als ob sie 
tut, was du ihr gesagt hast. Das ist ein signifikanter Unterschied. 
Programmierst du in C, so basiert das ganze zudem auf dem Model, das C 
dafür definiert.

Zum anderen gilt das alles nur und ausschließlich für eine Task alleine, 
und dort für einen Thread. Da ist es eine Selbstverständlichkeit, daß 
dein „Schreibe A, dann B“ usw. so ausgeführt wird, daß es für sich so 
aussieht, als ob es so passiert.

Der ganze Spaß mit Memory-Barriers, Synchronisation, und allem anderen 
beginnt erst, wenns über Thread- oder Taskgrenzen hinweg geht. Denn dann 
kommt genau das ins Spiel:

Andreas M. schrieb:
> Der interne RAM ist bei ARM z.B.
> üblicherweise als "Normal" in der MPU/MMU getaggt. D.h. am Memory Port
> der CPU kommen die Schreibzugriffe in irgend einer Reihenfolge raus. Es
> kann sogar sein, das die gar nicht rauskommen wenn sie zwischenzeitlich
> obsolet sind (Write Back Cache z.B.) Und es wird noch verrückter, der
> Speichercontroller darf die Zugriffe auch nochmal umsortieren so wie es
> ihm am besten passt.

Dann schreibt Task 1 erst A, dann B, und Task 2 wartet auf die 
Zugriffsreihenfolge, und soll  dann was tun, wenn das in der Reihenfolge 
passiert. Nur passiert das für die nie.

Die Lösung ist dann ganz einfach:

Andreas M. schrieb:
> man
> müsste überall memory barriers einfügen.

Genau so ist es.

Modernes C und C++ haben einen ganzen Stall voll von Atomic- und 
Memorysynchronisationsmethoden, um mit solchen Situationen umgehen zu 
können. C99 ist da halt auf das angewiesen, was der RTOS/- 
Speziallib-/Systembetreuer an Werkzeugen bereitstellt. Die Sprache 
selbst kann da nicht.

Oliver

: Bearbeitet durch User
von Andreas M. (amesser)


Lesenswert?

Oliver S. schrieb:
> Der ganze Spaß mit Memory-Barriers, Synchronisation, und allem anderen
> beginnt erst, wenns über Thread- oder Taskgrenzen hinweg geht. Denn dann
> kommt genau das ins Spiel:

Nein. Das ist Falsch. Für Synchronisation über Threads und Tasks sind 
Memory Barriers auf CPU Ebene weder gedacht noch notwendig. Lies die 
Doku von Intel, ARM oder von wem auch immer dazu. Memory Barriers werden 
ausschließlich dann benötigt wenn weitere Bus-Teilnehmer (z.B. 
Peripherien, DMA Controller, manchmal auch bei weiteren CPU Cores) 
beteiligt sind.

Was für Synchronisation hingegen gebraucht wird - wenn man C/c++ benutzt 
- ist ein Hinweis an den Compiler das bestimmte Zugriffe nicht 
vertauscht oder wegoptimiert werden dürfen. Und genau das erreicht man 
z.B. mit volatile oder aber auch mit memory clobbern.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Andreas M. schrieb:
> Und genau das erreicht man z.B. mit volatile oder aber
> auch mit memory clobbern.

Nur dass Memory Clobber kein C99 Sprachmittel ist.

Am ehesten entspricht das einem Funktionsaufruf den der Compiler nicht 
wegoptimieren kann und dessen Inhalt er nicht kennt.  Auch dafür gibt es 
keine C99 Sprachmittel, sondern man muss dann per Compileroption dafür 
sorgen, dass entsprechende Funktionen siese Eigenschaft haben, etwa 
indem man interprozedurale Optimierungen deaktiviert.

von Oliver S. (oliverso)


Lesenswert?

Andreas M. schrieb:
> Für Synchronisation über Threads und Tasks sind
> Memory Barriers auf CPU Ebene weder gedacht noch notwendig. Lies die
> Doku von Intel, ARM oder von wem auch immer dazu. Memory Barriers werden
> ausschließlich dann benötigt wenn weitere Bus-Teilnehmer (z.B.
> Peripherien, DMA Controller, manchmal auch bei weiteren CPU Cores)
> beteiligt sind.

Wenn da ein BS läuft, daß alles, was irgendwie parallel läuft, also 
Prozesse/Tasks/..., völlig unbekümmert auf die vorhandenen CPU-Cores 
verteilt, braucht es zur Synchronisation alles, was es halt so braucht.
Das dürfte beim TO nicht der Fall sein, aber im sonstigen Leben schon.

Oliver

von Andreas M. (amesser)


Lesenswert?

Johann L. schrieb:
> Am ehesten entspricht das einem Funktionsaufruf den der Compiler nicht
> wegoptimieren kann und dessen Inhalt er nicht kennt.  Auch dafür gibt es
> keine C99 Sprachmittel,

volatile gibt es auch in c99 und ist für die meisten Fälle ausreichend.

Oliver S. schrieb:
> braucht es zur Synchronisation alles, was es halt so braucht.

Nun der Satz ist zwar irgendwie richtig, bringt aber leider niemanden 
weiter. Zum Essen machen brauchst du auch alles was es dazu so braucht.

von Rick (rick)


Lesenswert?

Oliver S. schrieb:
> Andreas M. schrieb:
>> Das hat mit C gar nix zu tun.
>
> Doch, hat es. Wenn man in C programmiert, hat man halt nur die Mittel
> der Sprache zur Verfügung.

Jepp. Und asm ist in allen C-Standards seit C89/C90 vertreten:
https://en.cppreference.com/w/c/language/asm.html

von Harald K. (kirnbichler)


Lesenswert?

Aber:

> Unlike in C++, inline assembly is treated as an extension in C.
> It is conditionally supported and implementation defined,
> meaning that it may not be present and, even when provided
> by the implementation, it does not have a fixed meaning.

Das ist also ein "kann", kein "muss". Und wenn man weiterliest, stößt 
man schnell auf Einschränkungen, da werden konkrete verbreitete Compiler 
genannt, die gar keinen Inline-Assembler unterstützen.

Beitrag #8015296 wurde von einem Moderator gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rick schrieb:
> Und asm ist in allen C-Standards seit C89/C90 vertreten:
> https://en.cppreference.com/w/c/language/asm.html

Das bezieht sich aber ausschließlich auf die Syntax asm("text") und 
nicht auf eine irgendwie festgelegte Semantik.  Die natürlich auch nicht 
vom C/C++ Standard kommen kann.

asm("text") ist auch relativ nutzlos weil keine Operanden angegeben oder 
Seiteneffekte beschrieben werden können, firmal existiert also noch 
nicht einmal eine GCC Memory Barrier wie asm("" ::: "memory").

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.