Das ist nicht wirklich Eingang ; Sie führen eine Funktion nicht zweimal im selben Thread (oder in verschiedenen Threads) aus. Sie können dies durch Rekursion oder Übergabe der Adresse der aktuellen Funktion als Rückruffunktionszeigerarg an eine andere Funktion erhalten. (Und es wäre nicht unsicher, weil es synchron wäre).
Dies ist einfach nur Vanilla Data-Race UB (Undefined Behaviour) zwischen einem Signalhandler und dem Haupt-Thread: Nur dies sig_atomic_t
ist garantiert sicher . Andere funktionieren möglicherweise, wie in Ihrem Fall, in dem ein 8-Byte-Objekt mit einer Anweisung auf x86-64 geladen oder gespeichert werden kann, und der Compiler wählt zufällig diesen asm. (Wie die Antwort von @ icarus zeigt).
Siehe MCU-Programmierung - Unterbrechung der C ++ O2-Optimierung während der Schleife - Ein Interrupt-Handler auf einem Single-Core-Mikrocontroller ist im Grunde dasselbe wie ein Signal-Handler in einem Single-Threaded-Programm. In diesem Fall ist das Ergebnis des UB, dass eine Last aus einer Schleife gehoben wurde.
Ihr Testfall des Zerreißens aufgrund eines Datenrassen-UB wurde wahrscheinlich im 32-Bit-Modus oder mit einem älteren dümmeren Compiler entwickelt / getestet, der die Strukturelemente separat geladen hat.
In Ihrem Fall kann der Compiler die Speicher aus der Endlosschleife heraus optimieren, da kein UB-freies Programm sie jemals beobachten könnte. data
ist nicht _Atomic
odervolatile
, und es gibt keine anderen Nebenwirkungen in der Schleife. Es gibt also keine Möglichkeit, dass ein Leser mit diesem Schreiber synchronisiert. Dies geschieht tatsächlich, wenn Sie mit aktivierter Optimierung kompilieren ( Godbolt zeigt eine leere Schleife am unteren Rand von main). Ich habe auch die Struktur in zwei geändert long long
, und gcc verwendet einen einzelnen movdqa
16-Byte-Speicher vor der Schleife. (Dies ist nicht garantiert atomar, aber es ist in der Praxis auf fast allen CPUs, vorausgesetzt, es ist ausgerichtet, oder Intel überschreitet lediglich keine Cache-Zeilengrenze. Warum ist die Ganzzahlzuweisung für eine natürlich ausgerichtete Variable atomar auf x86? )
Das Kompilieren mit aktivierter Optimierung würde also auch Ihren Test unterbrechen und Ihnen jedes Mal den gleichen Wert anzeigen. C ist keine tragbare Assemblersprache.
volatile struct two_int
würde den Compiler auch zwingen, sie nicht zu optimieren, würde ihn aber nicht zwingen, die gesamte Struktur atomar zu laden / speichern. (Es wäre nicht aufhören es von so entweder zu tun, wenn.) Beachten Sie, dass volatile
sich keine Daten-Rennen UB vermeiden, aber es ist ausreichend für inter-thread Kommunikation in der Praxis und war , wie die Menschen von Hand gerollt atomics gebaut (zusammen mit Inline - asm) vor C11 / C ++ 11 für normale CPU-Architekturen. Sie sind Cache-kohärente so volatile
ist in der Praxis meist ähnlich wie _Atomic
mitmemory_order_relaxed
rein Last und pure-Speicher, wenn für Typen verwendeten schmal genug , dass der Compiler einen einzigen Befehl verwenden , damit Sie nicht Zerreißen erhalten. Und natürlichvolatile
Es gibt keine Garantien des ISO C-Standards für das Schreiben von Code, der mit _Atomic
und mo_relaxed auf dieselbe Weise kompiliert wird.
Wenn Sie eine Funktion hätten, die global_var++;
auf einem int
oder von einem Signalhandler long long
aus asynchron und asynchron ausgeführt wird, wäre dies eine Möglichkeit, die erneute Eingabe zum Erstellen eines Datenrassen-UB zu verwenden.
Abhängig davon, wie es kompiliert wurde (zu einem Speicherziel inkl. Hinzufügen oder hinzufügen oder zum Laden / Inkl / Speichern), wäre es in Bezug auf Signalhandler im selben Thread atomar oder nicht. Siehe Kann num ++ für 'int num' atomar sein? Weitere Informationen zur Atomizität unter x86 und in C ++. (C11s stdatomic.h
und _Atomic
Attribute bieten äquivalente Funktionen wie die std::atomic<T>
Vorlage von C ++ 11 )
Ein Interrupt oder eine andere Ausnahme kann nicht in der Mitte eines Befehls auftreten, daher ist das Hinzufügen eines Speicherziels atomar. Kontext schaltet eine Single-Core-CPU ein. Nur ein (Cache-kohärenter) DMA-Writer kann ein Inkrement von a add [mem], 1
ohne lock
Präfix auf einer Single-Core-CPU "betreten". Es gibt keine anderen Kerne, auf denen ein anderer Thread ausgeführt werden könnte.
Es ist also ähnlich wie bei Signalen: Ein Signalhandler wird anstelle der normalen Ausführung des Threads ausgeführt, der das Signal verarbeitet, sodass er nicht in der Mitte eines Befehls verarbeitet werden kann.