Wie erzwinge ich ein unbenutztes Speicherlesen in C, das nicht wegoptimiert wird?
Lesezeit: 8 Minuten
Richter Maygarden
Mikrocontroller erfordern häufig das Lesen eines Registers, um bestimmte Statusbedingungen zu löschen. Gibt es eine portable Methode in C, um sicherzustellen, dass ein Lesevorgang nicht wegoptimiert wird, wenn die Daten nicht verwendet werden? Reicht es aus, dass der Zeiger auf das speicherabgebildete Register als flüchtig deklariert ist? Mit anderen Worten, würde Folgendes immer auf standardkonformen Compilern funktionieren?
void func(void)
{
volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;
*REGISTER;
}
Ich verstehe, dass der Umgang mit Funktionen wie dieser zu Compiler-abhängigen Problemen führt. Meine Definition von tragbar ist in diesem Fall also etwas locker. Ich meine nur, dass es mit den gängigsten Toolchains so weit wie möglich funktionieren würde.
Die Inline-Montage ist normalerweise nicht optimiert. Wenn Sie es nicht plattformübergreifend wollen, ist es in Ordnung.
– Dmytro Sirenko
11. Dezember 2012 um 16:06 Uhr
@EarlGray Das ist eine sehr gültige Technik. Allerdings, und das ist pingelig, habe ich ein Beispiel, wo das eine zusätzliche Anweisung erfordern würde, weil der Compiler andernfalls das Laden einer Adresse optimieren könnte, indem er ein relatives Lesen von einer bereits geladenen Adresse verwendet. Natürlich habe ich Portabilität als Ziel angegeben und nicht Leistung …
– Richter Maygarden
11. Dezember 2012 um 16:16 Uhr
@Judge: FYI, es gibt eine weitere Besorgnis über den Unterschied zwischen dem Zugriff auf eine flüchtige Datei Objektund Zugriff auf ein Objekt mit einem flüchtigen lvalue-Ausdruck (stackoverflow.com/questions/13268657). Diese Frage bezieht sich auf C++, aber die gleiche Spitzfindigkeit könnte auch für C gelten.
– Steve Jessop
11. Dezember 2012 um 16:56 Uhr
Ich rufe eine in Assembly geschriebene Funktion GET32(addr) ldr r0 auf,[r0]; bx lr und habe nie Probleme. Ist portabler als Inline, natürlich etwas langsamer, hat aber andere Vorteile.
– Oldtimer
11. Dezember 2012 um 22:52 Uhr
zwöl
Die Leute streiten ziemlich heftig darüber, was genau volatile meint. Ich denke, die meisten Leute stimmen darin überein, dass das Konstrukt, das Sie zeigen, war beabsichtigt zu tun, was Sie wollen, aber es gibt keine allgemeine Vereinbarung, dass die Sprache im C-Standard ist garantiert es eigentlich ab C99. (Möglicherweise hat sich die Situation in C2011 verbessert; das habe ich noch nicht gelesen.)
Eine nicht standardmäßige, aber von eingebetteten Compilern ziemlich weithin unterstützte Alternative, die möglicherweise eher funktioniert, ist
(Das ‘volatile’ bezieht sich hier auf das ‘asm’ und bedeutet ‘dieses darf nicht gelöscht werden, obwohl es keine Ausgangsoperanden hat. Es ist nicht notwendig, es auch auf den Zeiger zu legen.)
Der größte verbleibende Nachteil dieses Konstrukts besteht darin, dass Sie immer noch keine Garantie dafür haben, dass der Compiler eine generiert eine Anweisung Speicher gelesen. Mit C2011 mit _Atomic unsigned intkönnte ausreichen, aber in Ermangelung dieser Funktion müssen Sie so ziemlich selbst einen echten (nicht leeren) Montageeinsatz schreiben, wenn Sie diese Garantie benötigen.
BEARBEITEN: Heute Morgen ist mir noch eine Falte aufgefallen. Wenn das Lesen aus dem Speicherplatz den Nebeneffekt hat, dass der Wert an diesem Speicherplatz geändert wird, müssen Sie
void func(void)
{
unsigned int *ptr = (unsigned int *)0x12345678;
asm volatile ("" : "=m" (*ptr) : "r" (*ptr));
}
um eine Fehloptimierung anderer Lesevorgänge von diesem Ort zu verhindern. (Um es 100 % klarzustellen, diese Änderung ändert nicht die Assemblersprache, für die generiert wird func selbst, kann aber die Optimierung des umgebenden Codes beeinträchtigen, insbesondere wenn func ist eingebettet.)
Ich kenne das Konstrukt nicht. Wie würde es aussehen, wenn die Adresse in einer Pointer-Variable statt in einer Konstanten wäre?
– Richter Maygarden
11. Dezember 2012 um 16:24 Uhr
@JudgeMaygarden ... : : "r" (*ptr). Die Klammern sind obligatorisch, aber was drin ist, ist nur ein normaler alter C-Ausdruck.
– zol
11. Dezember 2012 um 16:29 Uhr
Vielen Dank. Ich habe gerade herausgefunden, dass mir die Klammern um den Zeiger fehlten.
– Richter Maygarden
11. Dezember 2012 um 16:29 Uhr
Das funktioniert. Es ermöglicht auch, dass das Laden der Adresse aus dem Zeiger herausoptimiert wird. Ich hätte nicht gedacht, dass die zuverlässigste und portabelste Version Inline-Assembler verwenden würde. Das Setzen des Leseparameters ohne eigentliche Anweisung ist ein netter Trick!
– Richter Maygarden
11. Dezember 2012 um 16:33 Uhr
Ich habe dies in ein Makro verpackt und es funktioniert genauso wie mein Beispiel mit IAR EWARM. Ich überlege, in Zukunft zu GCC zu wechseln, und möchte lose Enden stützen. Ich muss noch beide Methoden in GCC testen.
– Richter Maygarden
11. Dezember 2012 um 16:44 Uhr
Lundin
Ja, der C-Standard garantiert, dass Code, der auf eine flüchtige Variable zugreift, nicht wegoptimiert wird.
C11 5.1.2.3/2
“Zugriff auf ein flüchtiges Objekt, ” … “sind alles Nebeneffekte”
C11 5.1.2.3/4
“Eine tatsächliche Implementierung muss einen Teil eines Ausdrucks nicht auswerten, wenn daraus abgeleitet werden kann, dass sein Wert nicht verwendet wird und dass keine erforderlichen Nebenwirkungen erzeugt werden (einschließlich solcher, die durch den Aufruf einer Funktion oder den Zugriff auf ein flüchtiges Objekt verursacht werden).”
C11 5.1.2.3/6
„Die Mindestanforderungen an eine konforme Implementierung sind:
— Zugriffe auf flüchtige Objekte werden streng nach den Regeln der abstrakten Maschine ausgewertet.”
Soweit ich mich erinnere, war diese Sprache alles in C99. Behandelt C2011 den spezifischen Punkt dessen, was als “Zugriff” qualifiziert wird? Das war in C99 implementierungsdefiniert, und das war der Streitpunkt, den Rodrigo, Steve Jessop und ich in den Kommentaren zu Rodrigos Antwort wiederholten.
– zol
12. Dezember 2012 um 16:45 Uhr
@Zack Nein, das 3. Zitat war nicht in C99, ich habe es überprüft, bevor ich es gepostet habe. Ich habe den Teil, der besagt, dass das, was als Zugriff gilt, implizit definiert ist, nicht zitiert.
– Ludin
12. Dezember 2012 um 18:46 Uhr
Meine Kopie von C99 ist zu Hause und ich bin es nicht, aber ich bin es sicher dass der Satz “Zugriffe auf flüchtige Objekte werden streng nach den Regeln der abstrakten Maschine ausgewertet” irgendwo in C99 aufgetaucht ist.
– zol
12. Dezember 2012 um 19:08 Uhr
@Zack Ah ja, du hast Recht, ich habe es bei C99 6.7.3/6 gefunden. Therefore any expression referring to such an [volatile] object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Es scheint, dass sie diesen Teil in C11 lediglich nach 5.1.2.3 verschoben haben.
– Ludin
12. Dezember 2012 um 19:20 Uhr
rodrigo
IIRC, der C-Standard ist ein bisschen locker in der Definition der Verwendung, so die *REGISTER wird nicht unbedingt so interpretiert, als würde man a tun lesen.
Aber folgendes sollte reichen:
int x = *REGISTER;
Das heißt, das Ergebnis der Speicherreferenz muss irgendwo verwendet werden. Das x muss jedoch nicht volatil sein.
AKTUALISIEREN: Um die Warnung der _unused-Variablen zu vermeiden, könnten Sie eine No-Op-Funktion verwenden. Eine statische und/oder Inline-Funktion sollte ohne Laufzeiteinbußen wegoptimiert werden:
AKTUALISIERUNG 2: Mir ist gerade eine nettere Funktion eingefallen:
static unsigned int read(volatile unsigned int *addr)
{
return *addr;
}
read(REGISTER);
Nun kann diese Funktion sowohl für Read-and-Use als auch für Read-and-Discard verwendet werden. 😎
Wenn x selbst nicht verwendet wird, reicht dies möglicherweise nicht aus.
– zol
11. Dezember 2012 um 16:02 Uhr
Dies würde eine Warnung für eine nicht verwendete Variable ausgeben. Ich erlaube keine Warnungen in veröffentlichtem Code, und das ist gut, um Fehler zu finden. Dies würde das Deaktivieren dieser Warnung in Funktionen erfordern, die diese Technik verwenden.
– Richter Maygarden
11. Dezember 2012 um 16:04 Uhr
Ich sage es nur ungern, aber … ein ausreichend aggressiver Compiler wird Ihren optimieren no_op Funktion und optimieren Sie dann die Last auf die jetzt nicht verwendeten x.
– zol
11. Dezember 2012 um 16:10 Uhr
@Zack: Selbst wenn der Compiler das Schreiben aggressiv optimiert, kann er das Lesen nicht optimieren, da es flüchtig ist! Das Problem ist, dass das nirgendwo in den C-Spezifikationen steht *x meint read the value at x; es bedeutet nur einen lvalue mit dem x die Anschrift. Es ist offensichtlich, wenn Sie schreiben *x = 0sollte es nicht lesen *x bevor Sie hineinschreiben.
– rodrigo
11. Dezember 2012 um 16:21 Uhr
@Zack Wie in dieses Papier? Interessante Lektüre, nichtsdestotrotz.
– Ludin
12. Dezember 2012 um 18:49 Uhr
Vielleicht werden GNU C-spezifische Erweiterungen nicht als sehr portabel angesehen, aber hier ist eine andere Alternative.
… wie in einer anderen Antwort vorgeschlagen, aber read1() verarbeitet unterschiedliche Registergrößen. Auch wenn ich nicht sicher bin, ob ich es verwenderead2() mit 8- oder 16-Bit-Registern jemals ein Problem darstellen, es gibt zumindest keine Warnungen zum Parametertyp.
Compiler optimieren normalerweise keine Assembly-Inlines (es ist schwierig, sie richtig zu analysieren). Darüber hinaus scheint es eine geeignete Lösung zu sein: Sie möchten eine explizitere Kontrolle über die Register und es ist natürlich für die Montage.
Da Sie einen Mikrocontroller programmieren, gehe ich davon aus, dass Ihr Code bereits Assembler enthält, sodass ein bisschen Inline-Assembler kein Problem darstellt.
Das OP fragte: “Gibt es einen tragbaren Weg …”. Inline-Assembler kommt also überhaupt nicht in Frage.
– Ludin
12. Dezember 2012 um 15:23 Uhr
@Lundin Das OP sagt, dass “… meine Definition von tragbar in diesem Fall etwas locker ist. Ich meine nur, dass es mit den beliebtesten Toolchains so weit wie möglich funktionieren würde”. Mein Weg ist zumindest über Toolchains hinweg tragbar (und die akzeptierte Antwort bestätigt meine Meinung)
– Dmytro Sirenko
12. Dezember 2012 um 15:31 Uhr
Da Inline-Assembler nicht vom C-Standard abgedeckt wird (anders als in C++), wäre er auch nicht zwischen Tools portierbar. Es gibt asm NOP; und __asm NOP; und asm NOP und asm ("NOP") und asm { NOP; } usw…
– Ludin
12. Dezember 2012 um 15:36 Uhr
Das OP fragte: “Gibt es einen tragbaren Weg …”. Inline-Assembler kommt also überhaupt nicht in Frage.
– Ludin
12. Dezember 2012 um 15:23 Uhr
@Lundin Das OP sagt, dass “… meine Definition von tragbar in diesem Fall etwas locker ist. Ich meine nur, dass es mit den beliebtesten Toolchains so weit wie möglich funktionieren würde”. Mein Weg ist zumindest über Toolchains hinweg tragbar (und die akzeptierte Antwort bestätigt meine Meinung)
– Dmytro Sirenko
12. Dezember 2012 um 15:31 Uhr
Da Inline-Assembler nicht vom C-Standard abgedeckt wird (anders als in C++), wäre er auch nicht zwischen Tools portierbar. Es gibt asm NOP; und __asm NOP; und asm NOP und asm ("NOP") und asm { NOP; } usw…
– Ludin
12. Dezember 2012 um 15:36 Uhr
13555900cookie-checkWie erzwinge ich ein unbenutztes Speicherlesen in C, das nicht wegoptimiert wird?yes
Die Inline-Montage ist normalerweise nicht optimiert. Wenn Sie es nicht plattformübergreifend wollen, ist es in Ordnung.
– Dmytro Sirenko
11. Dezember 2012 um 16:06 Uhr
@EarlGray Das ist eine sehr gültige Technik. Allerdings, und das ist pingelig, habe ich ein Beispiel, wo das eine zusätzliche Anweisung erfordern würde, weil der Compiler andernfalls das Laden einer Adresse optimieren könnte, indem er ein relatives Lesen von einer bereits geladenen Adresse verwendet. Natürlich habe ich Portabilität als Ziel angegeben und nicht Leistung …
– Richter Maygarden
11. Dezember 2012 um 16:16 Uhr
@Judge: FYI, es gibt eine weitere Besorgnis über den Unterschied zwischen dem Zugriff auf eine flüchtige Datei Objektund Zugriff auf ein Objekt mit einem flüchtigen lvalue-Ausdruck (stackoverflow.com/questions/13268657). Diese Frage bezieht sich auf C++, aber die gleiche Spitzfindigkeit könnte auch für C gelten.
– Steve Jessop
11. Dezember 2012 um 16:56 Uhr
Ich rufe eine in Assembly geschriebene Funktion GET32(addr) ldr r0 auf,[r0]; bx lr und habe nie Probleme. Ist portabler als Inline, natürlich etwas langsamer, hat aber andere Vorteile.
– Oldtimer
11. Dezember 2012 um 22:52 Uhr