Was macht __asm__ __volatile__ in C?

Lesezeit: 7 Minuten

Benutzeravatar von user3692521
Benutzer3692521

Ich habe mir etwas C-Code angesehen
http://www.mcs.anl.gov/~kazutomo/rdtsc.html

Sie verwenden Sachen wie __inline__, __asm__ usw wie folgt:

Code1:

static __inline__ tick gettick (void) {
    unsigned a, d;
    __asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) );
    return (((tick)a) | (((tick)d) << 32));
}

Code2:

volatile int  __attribute__((noinline)) foo2 (int a0, int a1) {
    __asm__ __volatile__ ("");
}

Ich habe mich gefragt, was Code1 und Code2 tun?

(Anmerkung des Herausgebers: Für diesen speziellen RDTSC-Anwendungsfall werden Intrinsics bevorzugt: How to get the CPU cycle count in x86_64 from C++? Siehe auch https://gcc.gnu.org/wiki/DontUseInlineAsm)

Benutzeravatar von Cory Nelson
Cory Nelson

Das __volatile__ Modifikator auf ein __asm__ block zwingt den Optimierer des Compilers, den Code unverändert auszuführen. Ohne sie könnte der Optimierer denken, dass sie entweder direkt entfernt oder aus einer Schleife gehoben und zwischengespeichert werden kann.

Dies ist nützlich für die rdtsc Anleitung so:

__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )

Dies erfordert keine Abhängigkeiten, sodass der Compiler davon ausgehen könnte, dass der Wert zwischengespeichert werden kann. Volatile wird verwendet, um es zu zwingen, einen neuen Zeitstempel zu lesen.

Bei alleiniger Verwendung wie folgt:

__asm__ __volatile__ ("")

Es wird nicht wirklich etwas ausgeführt. Sie können dies jedoch erweitern, um eine Speicherbarriere zur Kompilierzeit zu erhalten, die keine Neuordnung von Speicherzugriffsanweisungen zulässt:

__asm__ __volatile__ ("":::"memory")

Das rdtsc Anweisungen sind ein gutes Beispiel für volatile. rdtsc wird normalerweise verwendet, wenn Sie die Ausführungszeit einiger Anweisungen messen müssen. Stellen Sie sich einen Code wie diesen vor, wo Sie Zeit haben möchten r1 und r2Ausführung:

__asm__ ("rdtsc": "=a" (a0), "=d" (d0) )
r1 = x1 + y1;
__asm__ ("rdtsc": "=a" (a1), "=d" (d1) )
r2 = x2 + y2;
__asm__ ("rdtsc": "=a" (a2), "=d" (d2) )

Hier darf der Compiler den Zeitstempel tatsächlich zwischenspeichern, und eine gültige Ausgabe zeigt möglicherweise an, dass jede Zeile genau 0 Takte zur Ausführung benötigt hat. Offensichtlich ist das nicht das, was Sie wollen, also stellen Sie sich vor __volatile__ Caching verhindern:

__asm__ __volatile__("rdtsc": "=a" (a0), "=d" (d0))
r1 = x1 + y1;
__asm__ __volatile__("rdtsc": "=a" (a1), "=d" (d1))
r2 = x2 + y2;
__asm__ __volatile__("rdtsc": "=a" (a2), "=d" (d2))

Jetzt erhalten Sie jedes Mal einen neuen Zeitstempel, aber es besteht immer noch das Problem, dass sowohl der Compiler als auch die CPU alle diese Anweisungen neu anordnen dürfen. Es könnte dazu führen, dass die asm-Blöcke ausgeführt werden, nachdem r1 und r2 bereits berechnet wurden. Um dies zu umgehen, fügen Sie einige Barrieren hinzu, die die Serialisierung erzwingen:

__asm__ __volatile__("mfence;rdtsc": "=a" (a0), "=d" (d0) :: "memory")
r1 = x1 + y1;
__asm__ __volatile__("mfence;rdtsc": "=a" (a1), "=d" (d1) :: "memory")
r2 = x2 + y2;
__asm__ __volatile__("mfence;rdtsc": "=a" (a2), "=d" (d2) :: "memory")

Beachten Sie das mfence Anweisung hier, die eine CPU-seitige Barriere erzwingt, und der “Speicher”-Spezifizierer im flüchtigen Block, der eine Kompilierzeitbarriere erzwingt. Auf modernen CPUs können Sie ersetzen mfence:rdtsc mit rdtscp für etwas Effizienteres.

  • Also ist es bei einem leeren Block eine Art Anweisungsbarriere?

    – Bryan Chen

    20. Oktober 2014 um 0:20 Uhr

  • Beachten Sie, dass der Compiler nur die von ihm generierte statische Codereihenfolge steuern und vermeiden kann, Dinge zur Kompilierungszeit über diese Barriere hinaus zu verschieben, aber er kann die tatsächliche Ausführungsreihenfolge innerhalb der CPU nicht steuern, was sie möglicherweise noch ändert (die CPU weiß es nicht). über das flüchtige Attribut oder den leeren Codeblock). Mit rdtsc Dies kann möglicherweise zu einigen Ungenauigkeiten führen.

    – Leeor

    20. Oktober 2014 um 0:28 Uhr

  • @Leeor In der Tat, daher “Compile-Time-Barriere”.

    – Cory Nelson

    20. Oktober 2014 um 0:31 Uhr

  • Meistens ist der Code in der Frage einfach nur zum Kotzen. Es sollte die verwenden __rdtsc intrinsisch. volatile ist nutzlos drin asm volatile(""). Und Ihre Erklärung von Volatilität ist nicht gut, mit asm("rdtsc":... Der Compiler kann sogar die asm-Blöcke neu anordnen (oder sie entfernen, wenn a0 und d0 nicht verwendet werden), während with volatile Es muss sie in dieser Reihenfolge halten, kann aber die Hinzufügungen und Speicher immer noch verschieben.

    – Marc Glisse

    20. Oktober 2014 um 11:41 Uhr


  • Hinweis: Obwohl nicht besonders verwandt, rdtsc sollte zur Leistungsüberwachung vermieden werden, da viele Faktoren das Ergebnis verändern können.

    – edmz

    20. Oktober 2014 um 16:29 Uhr

asm dient zum Einbinden von nativem Assembly-Code in den C-Quellcode. Z.B

int a = 2;
asm("mov a, 3");
printf("%i", a); // will print 3

Compiler haben verschiedene Varianten davon. __asm__ sollte das auch sein, vielleicht mit Compiler-spezifischen Unterschieden.

volatile bedeutet, dass die Variable von außen (also nicht vom C-Programm) geändert werden kann. Zum Beispiel beim Programmieren eines Mikrocontrollers, wo die Speicheradresse 0x0000x1234 ist einer gerätespezifischen Schnittstelle zugeordnet (d.h. beim Programmieren für den GameBoy wird auf diese Weise auf Schaltflächen/Bildschirme/usw zugegriffen.)

volatile std::uint8_t* const button1 = 0x00001111;

Dies deaktivierte Compiler-Optimierungen, die darauf angewiesen sind *button1 nicht ändern, es sei denn, sie werden durch den Code geändert.

Es wird auch in der Multithread-Programmierung verwendet (heute nicht mehr benötigt?), wo eine Variable von einem anderen Thread geändert werden kann.

inline ist ein Hinweis für den Compiler, Aufrufe einer Funktion “inline” zu machen.

inline int f(int a) {
    return a + 1
}

int a;
int b = f(a);

Dies sollte nicht in einen Funktionsaufruf kompiliert werden f aber hinein int b = a + 1. Als ob f wo ein Makro. Compiler führen diese Optimierung meist automatisch je nach Funktionsnutzung/Inhalt durch. __inline__ in diesem Beispiel könnte eine spezifischere Bedeutung haben.

Ähnlich __attribute__((noinline)) (GCC-spezifische Syntax) verhindert, dass eine Funktion eingebettet wird.

  • Danke!! Und was ist der Vorteil von noinline?

    – Benutzer3692521

    19. Oktober 2014 um 23:43 Uhr

  • Ich denke, es stellt nur sicher, dass Sie anrufen foo2 wird in einen Funktionsaufruf einer leeren Funktion mit zwei ganzzahligen Argumenten und der Rückgabe einer ganzen Zahl in der Assembly übersetzt. Anstatt wegoptimiert zu werden. Diese Funktion könnte dann im generierten Assemblercode implementiert werden.

    – tmlen

    19. Oktober 2014 um 23:47 Uhr


  • Woher weiß es, dass es eine Ganzzahl (welche Ganzzahl?) Zurückgeben soll, wenn die Funktion leer ist?

    – Benutzer3692521

    19. Oktober 2014 um 23:49 Uhr

  • Ich würde sagen, dass sich volatile auf einem asm-Block ziemlich von volatile auf einer Variablen unterscheidet. Obwohl das gemeinsame Thema bleibt, nämlich dass es die Freiheiten des Optimierers einschränkt.

    – MVG

    20. Oktober 2014 um 8:31 Uhr

  • “Es wird auch in der Multithread-Programmierung verwendet (heute nicht mehr benötigt?), wo eine Variable von einem anderen Thread geändert werden könnte.” – Obwohl es tatsächlich verwendet wird, ist es falsch, da es nur die Befehlsreihenfolge von Zugriffen garantiert, nicht die Atomizität des Zugriffs auf den Speicher (obwohl der ausgerichtete Zugriff auf den meisten Architekturen atomar ist) oder Speicherzäune (mit Ausnahme der MSVC-Erweiterung – die auf ARM deaktiviert ist). Für eine ordnungsgemäße Verwendung ist es erforderlich, C(++)11-Atomics oder Compiler-Intrinsics zu verwenden.

    – Maciej Piechotka

    20. Oktober 2014 um 8:38 Uhr

Benutzeravatar von David C. Rankin
David C. Rankin

Das __asm__ Das Attribut gibt den Namen an, der im Assembler-Code für die Funktion oder Variable verwendet werden soll.

Das __volatile__ Qualifier, der im Allgemeinen im Real-Time-Computing eingebetteter Systeme verwendet wird, adressiert ein Problem mit Compiler-Tests der status register für die ERROR oder READY Bit verursacht Probleme während der Optimierung. __volatile__ wurde eingeführt, um dem Compiler mitzuteilen, dass das Objekt schnellen Änderungen unterliegt, und um zu erzwingen, dass jede Referenz des Objekts eine echte Referenz ist.

  • Nicht wirklich, es ist für alles mit Nebeneffekten, die Sie nicht mit Operandenbeschränkungen beschreiben können / können, zB wenn Sie möchten, dass es auch dann noch passiert, wenn alle Ausgabeoperanden nicht verwendet werden.

    – Peter Cordes

    24. Oktober 2017 um 9:40 Uhr

  • Ist es nicht das, was es aussagt, jede Referenz des Objekts zu einer echten Referenz zu zwingen? Der Grund, warum mich das „nicht wirklich“ etwas verwirrt, ist, dass die Beschreibung fast wörtlich aus der Referenzdokumentation übernommen wurde, wie sie im Oktober 2014 existierte. Ich werde sehen, ob ich das Zitat ausgraben kann.

    – David C. Rankin

    24. Oktober 2017 um 23:07 Uhr


  • Ich war größtenteils anderer Meinung als zu sagen, dass es nur für RTC relevant ist. Es geht nicht um „schnelle“ Veränderungen, sondern um alles, was Nebenwirkungen haben kann. Dass “jede Referenz eine echte Referenz” klingt wie eine Beschreibung der volatile Typqualifizierer (zB volatile int), nicht GNU C asm volatile. Bei Inline-ASM gibt es kein “das Objekt”.

    – Peter Cordes

    24. Oktober 2017 um 23:13 Uhr


  • Gotcha, ich denke, es wäre besser formuliert zu sagen volatile deaktiviert die Optimierung, die asm-Anweisungen verwirft, wenn sie feststellen, dass die Ausgabevariablen sowieso nicht benötigt werden :)

    – David C. Rankin

    24. Oktober 2017 um 23:15 Uhr

  • Ja, zuzüglich etwas Verhinderung von Nachbestellungen und mehr, wenn Sie a verwenden "memory" clobber, um es zu einer Compiler-Barriere zu machen.

    – Peter Cordes

    24. Oktober 2017 um 23:21 Uhr

1414240cookie-checkWas macht __asm__ __volatile__ in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy