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

Permet toutes les transformations de code qui ne modifient pas le comportement observable du programme.

Table des matières

Explication

Comportement observable d'un programme inclut les éléments suivants :

  • À chaque point de séquence , les valeurs de tous les objets volatile sont stables (les évaluations précédentes sont terminées, les nouvelles évaluations n'ont pas commencé).
(jusqu'à C++11)
  • Les accès (lectures et écritures) aux objets volatile se produisent strictement selon la sémantique des expressions dans lesquelles ils apparaissent. En particulier, ils ne sont pas réordonnancés par rapport aux autres accès volatiles sur le même thread.
(depuis C++11)
  • À la fin du programme, les données écrites dans les fichiers sont exactement comme si le programme avait été exécuté tel qu'écrit.
(until C++26)
  • Les données transmises à l'environnement hôte sont écrites dans les fichiers.
(since C++26)
  • Le texte d'invite qui est envoyé aux dispositifs interactifs sera affiché avant que le programme n'attende une entrée.
  • Si le pragme ISO C #pragma STDC FENV_ACCESS est pris en charge et est défini sur ON , les modifications apportées à l' environnement en virgule flottante (exceptions en virgule flottante et modes d'arrondi) sont garanties d'être observées par les opérateurs arithmétiques en virgule flottante et les appels de fonction comme s'ils étaient exécutés tels qu'écrits, sauf que
    • le résultat de toute expression en virgule flottante autre qu'un cast et une assignation peut avoir une plage et une précision d'un type en virgule flottante différent du type de l'expression (voir FLT_EVAL_METHOD ),
    • nonobstant ce qui précède, les résultats intermédiaires de toute expression en virgule flottante peuvent être calculés comme s'ils avaient une plage et une précision infinies (sauf si #pragma STDC FP_CONTRACT est OFF ).

Le compilateur C++ est autorisé à effectuer toute modification du programme tant que, pour une entrée donnée, le comportement observable du programme correspond à l'un des comportements observables possibles pour cette entrée.

Cependant, si une certaine entrée entraîne un comportement indéfini , le compilateur ne peut garantir aucun comportement observable du programme avec cette entrée, même si une opération du comportement observable se produit avant toute opération potentiellement indéfinie.

(jusqu'en C++26)

Un programme peut contenir des points de contrôle observables  .

Une opération OP est exempte d'indéfinition si pour toute opération indéfinie U , il existe un point de contrôle observable CP tel que OP se produit avant CP et CP se produit avant U . Le préfixe défini du programme avec une entrée donnée comprend toutes ses opérations exemptes d'indéfinition.

Le compilateur C++ est autorisé à effectuer toute modification du programme tant que, pour une entrée donnée, le comportement observable du préfixe défini du programme correspond à l'un des comportements observables possibles pour ce préfixe défini.

Si une certaine entrée entraîne un comportement indéfini , le compilateur ne peut garantir aucun comportement observable du programme avec cette entrée qui n'appartient pas au préfixe défini.

(depuis C++26)

Notes

Parce que le compilateur est (généralement) incapable d'analyser le code d'une bibliothèque externe pour déterminer si elle effectue ou non des E/S ou des accès volatiles, les appels aux bibliothèques tierces ne sont pas non plus affectés par l'optimisation. Cependant, les appels à la bibliothèque standard peuvent être remplacés par d'autres appels, éliminés ou ajoutés au programme pendant l'optimisation. Le code des bibliothèques tierces lié statiquement peut être soumis à l'optimisation au moment de l'édition des liens.

Les programmes comportant un comportement indéfini changent souvent de comportement observable lorsqu'ils sont recompilés avec différents paramètres d'optimisation. Par exemple, si un test pour le dépassement d'entier signé repose sur le résultat de ce dépassement, par exemple if ( n + 1 < n ) abort ( ) ; , il est entièrement supprimé par certains compilateurs car le dépassement signé est un comportement indéfini et l'optimiseur est libre de supposer que cela ne se produit jamais et que le test est redondant.

Élimination de copie est une exception à la règle as-if : le compilateur peut supprimer les appels aux constructeurs de déplacement et de copie ainsi que les appels correspondants aux destructeurs des objets temporaires, même si ces appels ont des effets secondaires observables.

new expression a une autre exception à la règle as-if : le compilateur peut supprimer les appels aux fonctions d'allocation remplaçables même si un remplacement défini par l'utilisateur est fourni et a des effets secondaires observables.

(depuis C++14)

Le nombre et l'ordre des exceptions en virgule flottante peuvent être modifiés par l'optimisation tant que l'état observé par la prochaine opération en virgule flottante est tel qu'aucune optimisation n'a eu lieu :

#pragma STDC FENV_ACCESS ON
for (i = 0; i < n; ++i)
    x + 1; // x + 1 est du code mort, mais peut lever des exceptions en virgule flottante
           // (sauf si l'optimiseur peut prouver le contraire). Cependant, l'exécuter n fois
           // lèvera la même exception à plusieurs reprises. Donc cela peut être optimisé en :
if (0 < n)
    x + 1;

Exemple

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;
}

Sortie :

# 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;

Voir aussi

Documentation C pour règle as-if