82 votes

Est-il permis à un compilateur d'optimiser une variable volatile locale?

Est le compilateur a permis d'optimiser ce (selon le C++17 standard):

int fn() {
    volatile int x = 0;
    return x;
}

à présent?

int fn() {
    return 0;
}

Si oui, pourquoi? Si non, pourquoi pas?


Voici un peu de réflexion sur ce sujet: compilateurs gcc compiler fn() comme une variable locale mis sur la pile, puis le retourner. Par exemple, sur x86-64, gcc crée ce:

mov    DWORD PTR [rsp-0x4],0x0 // this is x
mov    eax,DWORD PTR [rsp-0x4] // eax is the return register
ret    

Maintenant, autant que je sais que le standard ne veut pas dire qu'un volatile variable doit être mis sur la pile. Donc, cette version serait tout aussi bon:

mov    edx,0x0 // this is x
mov    eax,edx // eax is the return
ret    

Ici, edx stocke x. Mais maintenant, pourquoi s'arrêter là? En tant que edx et eax sont tous deux nuls, on pourrait simplement dire:

xor    eax,eax // eax is the return, and x as well
ret    

Et nous avons transformé fn() à la version optimisée. Est cette transformation est-elle valide? Si non, à quelle étape est-il invalide?

63voto

Matteo Italia Points 53117

Pas de. L'accès à l' volatile des objets est considéré comme les comportements observables, exactement comme I/S, sans distinction particulière entre les habitants et les variables globales.

Au moins les exigences de la conformité de la mise en œuvre sont:

  • L'accès à l' volatile des objets sont évalués strictement selon les règles de la machine abstraite.

[...]

Ces sont collectivement dénommés les comportements observables du programme.

N3690, [intro.exécution], numéro 8

Comment exactement ce qui est observable est en dehors de la portée de la norme, et tombe directement dans la mise en œuvre spécifiques au territoire, exactement comme les I/O et l'accès aux volatile objets. volatile "signifie" vous pensez que vous savez tout ce qui se passe ici, mais ce n'est pas comme ça; ayez confiance en moi et faire ce genre de choses sans être trop intelligent, parce que je suis dans votre programme de mon secret des choses avec votre octets". C'est en fait expliqué à [dcl.type.cv] ¶7:

[ Note: volatile est un indice pour la mise en œuvre d'éviter agressives d'optimisation impliquant l'objet parce que la valeur de l'objet peut être modifié par des moyens indétectable par une mise en œuvre. En outre, pour certaines implémentations, la volatilité pourrait indiquer que le matériel spécial instructions sont requis pour accéder à l'objet. Voir 1.9 détaillée de la sémantique. En général, la sémantique de la volatilité sont destinés à être le même en C++ comme ils sont en C. - la note de fin ]

12voto

rici Points 45980

Cette boucle peut être optimisé par la que-si la règle parce qu'il n'a pas de comportements observables:

for (unsigned i = 0; i < n; ++i) { bool looped = true; }

Ce qu'on ne peut:

for (unsigned i = 0; i < n; ++i) { volatile bool looped = true; }

La deuxième boucle n'quelque chose à chaque itération, ce qui signifie que la boucle prend O(n) fois. Je n'ai aucune idée de ce que la constante est, mais je peux le mesurer et puis j'ai une façon de occupé le bouclage d'un (plus ou moins) connus laps de temps.

Je peux le faire parce que la norme dit que l'accès à des produits volatils doivent se faire, dans l'ordre. Si un compilateur ont été de décider que, dans ce cas, la norme ne s'applique pas, je pense que j'aurais le droit de déposer un rapport de bogue.

Si le compilateur choisit de mettre looped dans un registre, je suppose que je n'ai pas de bon argument contre. Mais il doit encore définir la valeur de registre à 1 pour chaque itération de boucle.

10voto

Mehrdad Points 70493

Je vous prie d'être en désaccord avec l'opinion de la majorité, malgré le fait qu' volatile moyens observables I/O.

Si vous avez ce code:

{
    volatile int x;
    x = 0;
}

Je crois que le compilateur peut optimiser dans le cas de la règle, en supposant que:

  1. L' volatile variable n'est pas visible de l'extérieur, par exemple, les pointeurs (qui n'est évidemment pas un problème ici car il n'y a pas une telle chose dans le champ d'application)

  2. Le compilateur n'a pas vous fournir un mécanisme pour l'extérieur l'accès à celle - volatile

La raison en est simplement que tu ne pouvais pas observer la différence de toute façon, en raison du critère n ° 2.

Cependant, dans votre compilateur, critère n ° 2 ne peut pas être satisfait! Le compilateur peut essayer de vous fournir des garanties supplémentaires en observant volatile variables de "l'extérieur", comme par l'analyse de la pile. Dans de telles situations, le comportement vraiment est observable, de sorte qu'il ne peut pas être optimisé à l'écart.

Maintenant, la question est, est le code suivant toute différente de celle que ci-dessus?

{
    volatile int x = 0;
}

Je crois que j'en ai observé un comportement différent pour cette dans Visual C++ à l'égard de l'optimisation, mais je ne suis pas entièrement sûr de savoir sur quelles bases. Il se peut que l'initialisation ne compte pas comme "accès"? Je ne suis pas sûr. Ce qui peut être intéressant est une autre question si vous êtes intéressé, mais sinon, je crois que la réponse est comme je l'ai expliqué ci-dessus.

6voto

berendi Points 3531

Théoriquement, un gestionnaire d'interruption pourrait

  • vérifiez si l'adresse de retour correspond à la fonction fn() . Il peut accéder à la table des symboles ou aux numéros de ligne source via l'instrumentation ou les informations de débogage jointes.
  • puis modifiez la valeur de x , qui serait stockée à un décalage prévisible par rapport au pointeur de pile.

… Faisant ainsi fn() retourner une valeur non nulle.

6voto

Tezra Points 4908

Je vais juste ajouter une référence détaillée pour le cas de la règle et de la volatilité de mot-clé. (Au bas de ces pages, de suivre les "voir aussi" et "Références" à remonter à l'origine des specs, mais je trouve cppreference.com beaucoup plus facile à lire/comprendre.)

En particulier, je veux que vous lisiez cet article

volatile de l'objet - un objet dont le type est volatile-qualifiés, ou un sous-objet d'un objet volatile, ou une mutable sous-objet d'un const volatile de l'objet. Tous les accès (lecture ou d'écriture, membre de l'appel de fonction, etc.) fait au travers d'un glvalue expression de la volatilité des qualifiés de type est traité comme un visible des effets secondaires pour l'application de l'optimisation (qui est, dans un seul thread d'exécution, accès volatiles ne peut pas être optimisé ou réorganisée avec un autre visible effet secondaire qui est séquencé en-avant ou séquencés-après de la volatilité de l'accès. Cela rend volatile des objets appropriés pour la communication avec un gestionnaire de signal, mais pas avec un autre thread d'exécution, voir std::memory_order). Toute tentative de se référer à un volatile de l'objet par l'intermédiaire d'un non-volatile glvalue (par exemple par le biais d'une référence ou un pointeur non volatile de type) entraîne un comportement indéfini.

Si le mot clé volatile spécifiquement est sur la désactivation de l'optimisation du compilateur sur glvalues. La seule chose ici, le mot clé volatile peut affecter est peut-être return x, le compilateur peut faire ce qu'il veut avec le reste de la fonction.

Combien le compilateur peut optimiser le rendement dépend de la façon dont beaucoup le compilateur est permis d'optimiser l'accès à x dans ce cas (car il n'est pas réorganisation de quoi que ce soit, et à proprement parler, n'est-ce pas la suppression de l'expression de renvoi. Il y a l'accès, mais il est la lecture et l'écriture de la pile, ce qui est devrait être en mesure de rationaliser.) Donc, comme je l'ai lu, c'est une zone grise dans la façon dont beaucoup le compilateur est autorisé à optimiser, et peut facilement être soutenu dans les deux sens.

Remarque: Dans ces cas, supposons toujours que le compilateur va faire le contraire de ce que vous voulez/besoin. Vous pouvez soit désactiver l'optimisation (au moins pour ce module), ou à essayer de trouver un comportement défini pour ce que vous voulez. (C'est aussi pourquoi les tests unitaires est tellement important) Si vous croyez que c'est un défaut, vous devez apporter avec les développeurs de C++.


Tout cela est encore très difficile à lire, donc, essayer d'inclure ce que je trouve pertinents, de sorte que vous pouvez lire vous-même.

glvalue Un glvalue expression est soit lvalue ou xvalue.

Propriétés:

Un glvalue peut être implicitement converti en prvalue avec lvalue-à-rvalue, tableau de pointeur, ou de la fonction de pointeur implicite la conversion. Un glvalue peut être polymorphe: le type dynamique de l' objet il identifie n'est pas nécessairement le type statique de l' de l'expression. Un glvalue peuvent être incomplètes type, lorsque cela est autorisé par la de l'expression.


xvalue, Les expressions suivantes sont xvalue expressions:

un appel de fonction ou un opérateur surchargé d'expression, dont le retour type référence rvalue pour objet, comme std::move(x); a[n], la intégré dans l'indice de l'expression, où l'un des opérandes est un tableau rvalue ; un.m, le membre de l'objet de l'expression, où a est une rvalue et m est un non-membre de données statiques de non-type de référence; un.*mp, le pointeur de membre de l'objet de l'expression, où a est une rvalue et mp est un pointeur pour le membre de données; un ? b : c, le ternaire expression conditionnelle pour certains b et c (voir la définition de détail); une expression cast à rvalue de référence pour le type d'objet, comme static_cast(x); toute expression qui désigne un objet temporaire, après temporaire la matérialisation. (depuis C++17) Propriétés:

Même que rvalue (ci-dessous). Même que glvalue (ci-dessous). En particulier, comme tous les rvalues, xvalues se lier à des références rvalue, et comme tous les glvalues, xvalues peut être polymorphe, et non de la classe xvalues peut être de cv qualifiés.


lvalue, Les expressions suivantes sont lvalue expressions:

le nom d'une variable, une fonction, ou un membre de données, indépendamment de type, comme std::cin ou std::endl. Même si le type de la variable est référence rvalue, l'expression composée de son nom est une lvalue expression; un appel de fonction ou un opérateur surchargé d'expression, dont le type de retour est lvalue de référence, tels que les std::getline(std::cin, str), std::cout << 1, str1 = str2, ou ++il; a = b, a += b, a %= b, et tous les autres intégrée d'attribution et d'assignation composé expressions; ++un et-un, le haut-pré-incrémentation et de pré-décrémentation expressions; *p, le haut-indirection expression; a[n] et p[n], le haut-indice des expressions, sauf si a est une matrice de rvalue (depuis C++11); une.m, le membre de l'objet de l'expression, à l'exception de, où m est un membre de l'agent recenseur ou d'une fonction membre statique, ou où a est un rvalue et m est un non-membre de données statiques de non-type de référence; p->m, le membre intégré de pointeur expression, à l'exception de, où m est un membre agent recenseur ou d'une fonction membre statique; un.*mp, le pointeur de membre de l'objet de l'expression, où a est une lvalue et mp est un pointeur pour le membre de données; p->*mp, le pointeur de membre de pointeur l'expression, où mp est un pointeur vers les données de membre; a, b, intégré dans virgule expression, où b est une lvalue; un ? b : c, le ternaire expression conditionnelle pour certains, b et c (par exemple, lorsque les deux sont lvalues du même type, mais voir la définition de détail); un littéral de chaîne, comme "Hello, world!"; un casting expression de lvalue type de référence, comme static_cast(x); un appel de fonction ou une surcharge opérateur d'expression, dont le type de retour est rvalue référence à fonction; une expression cast de référence rvalue au type de fonction, par exemple comme static_cast(x). (depuis C++11) Propriétés:

Même que glvalue (ci-dessous). L'adresse d'une lvalue peuvent être prises: &++i1 et &std::endl sont des expressions valides. Modifiable lvalue peut être utilisé que la gauche opérande de cession et composé opérateurs d'affectation. Une lvalue peut être utilisé pour initialiser une lvalue de référence; on associe un nouveau nom à l'objet identifié par l'expression.


comme-si la règle

Le compilateur C++ est autorisé à effectuer les modifications apportées au programme aussi longtemps que la suite reste vrai:

1) À chaque point de séquence, les valeurs de tous les volatiles objets sont stables (évaluations précédentes sont complètes, de nouvelles évaluations de ne pas commencé) (jusqu'à ce que C++11) 1) Accès (en lecture et en écriture) à la volatilité des objets se produisent strictement en fonction de la sémantique des expressions dans lesquelles ils se produisent. En particulier, ils ne sont pas réorganisées à l'égard des autres accès volatiles sur le même thread. (depuis C++11) 2) À la fin du programme, les données écrites dans les fichiers est exactement comme si le programme a été exécuté comme à l'écrit. 3) texte d'Invite qui est envoyé aux dispositifs interactifs seront indiqués avant le programme attend d'entrée. 4) Si l'ISO C pragma #pragma STDC FENV_ACCESS est pris en charge et est réglé SUR on, les modifications apportées à la virgule flottante de l'environnement (les exceptions de virgule flottante et les modes d'arrondi) sont garantis d'être observé par l'arithmétique à virgule flottante opérateurs et des appels de fonction que si elle est exécutée comme l'écrit, sauf que le résultat d'une expression à virgule flottante autre que la fonte et l'affectation peut avoir une autonomie et une précision de virgule flottante de type 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 à virgule flottante peut être calculé comme si, à l'infini et de précision (sauf #pragma STDC FP_CONTRACT est DÉSACTIVÉ)


Si vous voulez lire les specs, je crois que ce sont ceux dont vous avez besoin pour lire

Références

C11 norme (ISO/IEC 9899:2011): 6.7.3 Type qualificatifs (p: 121-123)

Standard C99 (ISO/IEC 9899:1999): 6.7.3 Type qualificatifs (p: 108-110)

C89/C90 norme (ISO/IEC 9899:1990): 3.5.3 Type qualificatifs

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X