Namespaces
Variants

The as-if rule

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Erlaubt jegliche und alle Code-Transformationen, die das beobachtbare Verhalten des Programms nicht ändern.

Inhaltsverzeichnis

Erklärung

Beobachtbares Verhalten eines Programms umfasst Folgendes:

  • An jedem Sequenzpunkt sind die Werte aller volatile -Objekte stabil (vorherige Auswertungen sind abgeschlossen, neue Auswertungen noch nicht begonnen).
(bis C++11)
  • Zugriffe (Lese- und Schreiboperationen) auf volatile -Objekte erfolgen strikt gemäß der Semantik der Ausdrücke, in denen sie vorkommen. Insbesondere werden sie nicht neu geordnet in Bezug auf andere volatile-Zugriffe im selben Thread.
(seit C++11)
  • Beim Programmende entsprechen die in Dateien geschriebenen Daten exakt denen, die bei Ausführung des Programms gemäß seinem Quellcode entstanden wären.
(until C++26)
  • An die Host-Umgebung übergebene Daten werden in Dateien geschrieben.
(since C++26)
  • Aufforderungstext, der an interaktive Geräte gesendet wird, wird angezeigt, bevor das Programm auf Eingaben wartet.
  • Falls das ISO C-Pragma #pragma STDC FENV_ACCESS unterstützt wird und auf ON gesetzt ist, werden Änderungen an der Gleitkommaumgebung (Gleitkomma-Ausnahmen und Rundungsmodi) garantiert von den Gleitkomma-Operatoren und Funktionsaufrufen beachtet, als ob sie wie geschrieben ausgeführt würden, außer dass
    • das Ergebnis jedes Gleitkomma-Ausdrucks außer Typumwandlung und Zuweisung Bereich und Genauigkeit eines Gleitkommatyps haben kann, der vom Typ des Ausdrucks abweicht (siehe FLT_EVAL_METHOD ),
    • ungeachtet des oben Gesagten können Zwischenergebnisse jedes Gleitkomma-Ausdrucks berechnet werden, als ob mit unendlichem Bereich und unendlicher Genauigkeit (es sei denn, #pragma STDC FP_CONTRACT ist auf OFF gesetzt).

Dem C++-Compiler ist es erlaubt, beliebige Änderungen am Programm vorzunehmen, solange bei gleicher Eingabe das beobachtbare Verhalten des Programms einem der möglichen beobachtbaren Verhalten entspricht, die zu dieser Eingabe gehören.

Wenn jedoch eine bestimmte Eingabe zu undefiniertem Verhalten führt, kann der Compiler kein beobachtbares Verhalten des Programms mit dieser Eingabe garantieren, selbst wenn ein Teil des beobachtbaren Verhaltens vor einem möglichen undefinierten Vorgang stattfindet.

(bis C++26)

Ein Programm kann beobachtbare Checkpoints  enthalten.

Ein Vorgang OP ist undefiniert-frei , wenn für jeden undefinierten Vorgang U ein beobachtbarer Checkpoint CP existiert, sodass OP vor CP stattfindet und CP vor U stattfindet. Der definierte Präfix des Programms mit einer gegebenen Eingabe umfasst alle seine undefiniert-freien Vorgänge.

Dem C++-Compiler ist es erlaubt, beliebige Änderungen am Programm vorzunehmen, solange bei gleicher Eingabe das beobachtbare Verhalten des definierten Präfixes des Programms einem der möglichen beobachtbaren Verhalten entspricht, die zu diesem definierten Präfix gehören.

Wenn eine bestimmte Eingabe zu undefiniertem Verhalten führt, kann der Compiler kein beobachtbares Verhalten des Programms mit dieser Eingabe garantieren, das nicht zum definierten Präfix gehört.

(seit C++26)

Hinweise

Da der Compiler (normalerweise) nicht in der Lage ist, den Code einer externen Bibliothek zu analysieren, um festzustellen, ob sie E/A- oder flüchtige Zugriffe durchführt oder nicht, sind Aufrufe von Drittanbieter-Bibliotheken ebenfalls nicht von der Optimierung betroffen. Allerdings können Standardbibliotheksaufrufe während der Optimierung durch andere Aufrufe ersetzt, entfernt oder dem Programm hinzugefügt werden. Statisch verknüpfter Drittanbieter-Bibliothekscode kann einer Linkzeit-Optimierung unterzogen werden.

Programme mit undefiniertem Verhalten ändern oft ihr beobachtbares Verhalten, wenn sie mit unterschiedlichen Optimierungseinstellungen neu kompiliert werden. Beispielsweise wird, wenn ein Test auf vorzeichenbehafteten Integer-Überlauf auf dem Ergebnis dieses Überlaufs basiert, z.B. if ( n + 1 < n ) abort ( ) ; , dieser von einigen Compilern vollständig entfernt , weil vorzeichenbehafteter Überlauf undefiniertes Verhalten ist und der Optimierer davon ausgehen kann, dass dies niemals geschieht, und der Test somit redundant ist.

Copy Elision ist eine Ausnahme von der as-if-Regel: Der Compiler kann Aufrufe von Move- und Copy-Konstruktoren sowie die entsprechenden Aufrufe der Destruktoren temporärer Objekte entfernen, selbst wenn diese Aufrufe beobachtbare Seiteneffekte haben.

new expression hat eine weitere Ausnahme von der as-if-Regel: Der Compiler kann Aufrufe der replaceable allocation functions entfernen, selbst wenn eine benutzerdefinierte Ersetzung bereitgestellt wird und beobachtbare Seiteneffekte hat.

(since C++14)

Die Anzahl und Reihenfolge von Gleitkomma-Ausnahmen kann durch Optimierung geändert werden, solange der Zustand, wie er von der nächsten Gleitkomma-Operation wahrgenommen wird, so ist, als hätte keine Optimierung stattgefunden:

#pragma STDC FENV_ACCESS ON
for (i = 0; i < n; ++i)
    x + 1; // x + 1 ist toter Code, kann aber FP-Ausnahmen auslösen
           // (sofern der Optimierer nicht das Gegenteil beweisen kann). Die Ausführung n-mal
           // wird jedoch dieselbe Ausnahme wiederholt auslösen. Daher kann dies optimiert werden zu:
if (0 < n)
    x + 1;

Beispiel

int& preinc(int& n) { return ++n; }
int add(int n, int m) { return n + m; }
// volatile input to prevent constant folding
volatile int input = 7;
// volatile output to make the result a visible side-effect
volatile int result;
int main()
{
    int n = input;
// using built-in operators would invoke undefined behavior
//  int m = ++n + ++n;
// but using functions makes sure the code executes as-if 
// the functions were not overlapped
    int m = add(preinc(n), preinc(n));
    result = m;
}

Ausgabe:

# full code of the main() function as produced by the GCC compiler
# x86 (Intel) platform:
        movl    input(%rip), %eax   # eax = input
        leal    3(%rax,%rax), %eax  # eax = 3 + eax + eax
        movl    %eax, result(%rip)  # result = eax
        xorl    %eax, %eax          # eax = 0 (the return value of main())
        ret
# PowerPC (IBM) platform:
        lwz 9,LC..1(2)
        li 3,0          # r3 = 0 (the return value of main())
        lwz 11,0(9)     # r11 = input;
        slwi 11,11,1    # r11 = r11 << 1;
        addi 0,11,3     # r0 = r11 + 3;
        stw 0,4(9)      # result = r0;
        blr
# Sparc (Sun) platform:
        sethi   %hi(result), %g2
        sethi   %hi(input), %g1
        mov     0, %o0                 # o0 = 0 (the return value of main)
        ld      [%g1+%lo(input)], %g1  # g1 = input
        add     %g1, %g1, %g1          # g1 = g1 + g1
        add     %g1, 3, %g1            # g1 = 3 + g1
        st      %g1, [%g2+%lo(result)] # result = g1
        jmp     %o7+8
        nop
# in all cases, the side effects of preinc() were eliminated, and the
# entire main() function was reduced to the equivalent of result = 2 * input + 3;

Siehe auch

C-Dokumentation für as-if rule