Nous savons que logique-et opérateur ( Mais je me demande si l'optimiseur de compilateur peut jamais réorganiser les instructions d'accès à la mémoire pour (Considérez les deux && code>) garantit une évaluation de gauche à droite. * A code> et B-> FOO code> Dans le code suivant, c'est-à-dire que l'optimiseur écrit des instructions qui tentent d'accéder à * b code> avant d'accéder à * A code>. p> A code> et B code> pour être des pointeurs sur des régions de mémoire dans le tas.) P> if (*a && b->foo) {
/* do something */
}
4 Réponses :
Selon la norme ISO C11, à §C, l'annexe C, elle a déclaré que P>
Les points de séquence sont décrits dans ... entre les évaluations des premier et second opérandes des opérateurs suivants: logique et && (6.5.13); logique ou || (6.5.14); virgule, (6.5.17). P> blockQuote>
et, comme indiqué au § 5.1.2.3: P>
séquencé précédemment est une relation asymétrique, transitive et judiciaire entre les évaluations exécutées par un seul fil, qui induit un ordre partiel entre ces évaluations. Étant donné que deux évaluations A et B, si A est séquencée avant B, l'exécution d'A doit précéder l'exécution de b. P> blockQuote>
Il est donc garanti que le premier opérande est évalué avant la seconde. Aucune optimisation de sécurité ne devrait être possible dans cette circonstance. P>
La même chose est aussi garantie pour les points-virgules, non? Alors pourquoi avons-nous besoin de barrière de mémoire de niveau de compilation dans le code fourni dans cette réponse? - Stackoverflow.com/a/14983432
L'accès à la mémoire n'est pas identique à l'évaluation d'une expression, l'OP n'a pas spécifié que int * d code> est volatile afin que le compilateur soit sûr de la préciser. Aucune réorganisation d'instructions qui provoque l'évaluation apparaît réellement dans la question que vous avez fournie.
@Jack Comment cela correspond-il à la règle "comme-si" et " les descriptions sémantiques de cette norme internationale décrivent le comportement d'une machine abstraite dans laquelle des problèmes d'optimisation ne sont pas pertinents. I>"?
@ALEXD: Il me semble clair que la réorganisation des conditions pourrait rompre la règle AS-si elle pourrait être un comportement observable qui change en fonction de l'ordre (réfléchissez simplement à une déclaration précédente struct FOO * B = * A? & Valeur : Null code>)
@Jack n'est pas observable parce que la faute est également spéculative. Le système peut continuer à traiter l'autre expression jusqu'à ce que sa capacité à fonctionner de manière spéculativement et conditionnelle soit dépassée. Cela pourrait être après une faute. (Et, en fait, c'est sur la plupart des processeurs modernes. Sinon, une extraction spéculative serait impossible.)
Mais je me demande si le compilateur Optimizer peut déjà réorganiser les instructions d'accès à la mémoire pour * A et B-> FOO dans le code suivant, c'est-à-dire que l'optimiseur écrit des instructions qui tentent d'accéder à * B avant d'accéder à * A. P. >
if (*a && b->foo) { /* do something */ }
Et si cela peut être prouvé que B code> n'est pas nul, mais il ne peut pas être prouvé si B code> pointe vers une mémoire allouée valide?
@LONELEARNER Le système peut ensuite effectuer une exécution spéculative et décider plus tard de jeter les résultats de celui-ci.
@LonElearner J'ai réécrit le dernier bit pour clarifier.
Tout d'abord, je le supporte comme étant accordé que Je pense que le compilateur peut légitimement effectuer des évaluations de la sous-expression droite de la droite de l'opérateur Pour votre exemple, le compilateur C ++ est autorisé à introduire une réorganisation dans les conditions suivantes: P>
Si 1. ne contient pas, alors l'opérateur si 2. ne contient pas, alors l'opérateur Si 3. ne peut pas être prouvé par analyse statique, la réorganisation introduirait un comportement non défini qui n'est pas dans le programme d'origine. P>
C compilateur C, naturellement, n'a besoin que d'effectuer la 3ème chèque. p>
En fait, même si && code> correspond à la version intégrée du logique et de l'opérateur. P>
&& code> avant de terminer l'évaluation du côté gauche, mais de manière à ce que ce soit 't changer la sémantique de l'expression complète. p>
A code> est un pointeur primitif (c'est-à-dire que son type n'est pas une classe qui surcharge opérateur * code>). li>
B code> est un pointeur primitif (c'est-à-dire que son type n'est pas une classe qui surcharge opérateur -> code>) li>
B code> est connu pour être latéralable indépendamment de la valeur de * A code> li>
ol>
défini par l'utilisateur * > peut avoir un effet secondaire de modification de la valeur de B-> foo code>. P >
défini par l'utilisateur -> code> peut modifier la valeur de * A code> ou lancer ou produire un autre côté observable effet (par exemple, imprimer quelque chose) qui n'aurait pas dû être montré avait * a code> évalué à false code>. p>
* A code> et B-> foo code> impliquent la surcharge de l'opérateur, C ++ compiler peut toujours réorganiser quelques instructions lorsque ces opérateurs peuvent être inlinés et que le compilateur n'a pas 't détecte quelque chose de dangereux. p>
C a-t-il une surcharge sur l'opérateur?
@Meathervane n'a pas fait attention aux tags. Merci d'avoir attrapé ça.
Le système peut évaluer C'est donc purement à la hauteur des capacités du compilateur, de la CPU et d'autres composants du système. Tant qu'il peut s'assurer qu'il n'y ait aucune conséquence visible pour la conformité du code, il peut exécuter (presque) tout ce qu'il veut (presque) à tout moment. P> b-> foo code> jusqu'à ce qu'il frappe quelque chose qui dépasse sa capacité à exécuter de manière spéculativement. La plupart des systèmes modernes peuvent gérer une défaillance spéculative et ignorer le défaut s'il s'avère que les résultats de l'opération ne sont jamais utilisés. P>
Je pense que la réponse consiste à mélanger compilateur as-si code> avec les capacités de la CPU. Le compilateur, dans ce cas, devrait être autorisé à réorganiser les instructions, bien que le matériel / la CPU puisse.
@ Myste il n'y a pas de différence. Le compilateur dit à la CPU Que faire. Les normes ne distinguent pas. Du point de vue du code C, cela ne fait aucune différence ce qui rend l'optimisation. J'ai fait un choix conscient de ne pas laisser la dénonciation rendre ma réponse plus confuse.
Juste pour corriger mon commentaire d'avant, je voulais écrire: "Le compilateur, dans ce cas, devrait être autorisé à réorganiser les instructions, bien que le matériel / la CPU soit" ... À cela ne faisant aucune différence pratique, vous avez probablement raison, mais je pense que cela aide à savoir où se produisent des optimisations. Il y a plus de confiance humaine dans les optimisations de la CPU puis des optimisations du compilateur (il y a la peur que le compilateur «mal compris» notre intention). Si cette peur est illogique ou non est probablement à côté du point.
Pourquoi le compilateur ne devrait-il pas être autorisé à réorganiser les instructions? Si le compilateur peut générer du code qui exécute les deux parties simultanément, mais peut remplacer la seconde si le premier ne le rend pas nécessaire, il peut donc certainement générer ce code. Dans la pratique, cela n'aura probablement pas cette capacité. Mais en tant que programmeur C, vous ne devriez pas vous en soucier.
Personnellement, je me fiche de la façon dont la machine gère ses tâches, tant que je reçois les résultats que j'ai demandé ... Mais il s'agit d'une optimisation orientée d'exécution, elle ne peut probablement pas être exécutée efficacement par le code de la machine. C'est une question de modularisation, le code de la machine doit indiquer au matériel quelles instructions à effectuer et (tout comme le compilateur), le matériel doit effectuer des actions qui fournissent le même résultat ... Par conséquent, le compilateur doit créer des instructions claires qui Le matériel peut fonctionner de la meilleure manière optimisée d'exécution.
@MYST Je ne sais pas ce que vous entendez par "clair". Les jours de la génération de code d'assemblage simple simple et simple sont longs. Les optimisations modernes du compilateur sont incroyablement complexes et sophistiquées.
... Je suppose que je suis romantique. De toute évidence, vous avez raison quand il s'agit de questions pratiques, mais je m'efforce toujours de l'approche modulaire «boîte noire» idéale. Je pensais juste que cela mériterait de mentionner les différentes préoccupations - le compilateur étant responsable des optimisations d'instructions (c'est-à-dire une meilleure ASM), tandis que le matériel étant chargé des optimisations d'exécution (c'est-à-dire la mise en cache d'instructions à terme). Ceci, le long des contraintes standard C, empêche le compilateur de modifier la séquence des instructions mentionnées par l'OP.
et dans quel scénario il peut i> arriver?
Oui. Voir AS-Si règle.
Je pensais que
&& code> a provoqué un point de séquence. Ainsi, aucune récupération / évaluation deB code> doit* A code> être faux. HMM @ALEXD AS-SI-SI témoigne de la contrepartie.@chux et vous êtes très vrai. Ref: 6.5.13 / P4, C11.
@Souravghosh Si c'était si simple, nous n'avions pas besoin de barrière de mémoire de niveau de compilation pour Stackoverflow.com/a/14983432/1175080 , devons nous?
@LonElearner Je pense que vous êtes mal compris. :)
@ALEXD: récupération
B-> FOO code> n'est d'abord pas aussi conforme si le compilateur peut prouver queB code> quelque part valide.@Alexd Si la réponse est "oui", pouvons-nous tromper le compilateur de violer la règle AS-si. Exemple: le programmateur sait que
* a code> est un entier (booléen) qui indique si le tampon queB code> a été attribué. Le compilateur peut ne pas connaître cette chose à propos de la logique du programme et peut émettre des instructions pour chargerb-> foo code> avant* a code> même si* a = 0 code> et conduit ainsi à SIGSEGV au moment de l'exécution?@Souravghosh
&& code> et; code> sont des points de séquence. J'ai donc interprété "pensé" = "Savoir que c'est vrai". Mais je ne vois pas comment ce point de séquence répond à cette question ou explique le besoin de barrière mémoire de niveau de compilation à Stackoverflow.com/a/ 14983432/1175080 . Les points de séquence assure l'ordre d'évaluation des expressions. Dans cette question, je suis préoccupé par l'ordre des accès à la mémoire (c'est-à-dire les instructions de chargement dans le code de la machine émises par le compilateur).@ user2357112 C'est si cela peut différer de défaut jusqu'à ce qu'il sait qu'il a besoin de l'accès, quels processeurs aujourd'hui peuvent réellement faire.
@LONELEARNER Le SIGSEGV serait spéculatif, tout comme la récupération serait. Il ne serait pas agi jusqu'à ce qu'il soit connu pour être valide. Les systèmes font réellement cela aujourd'hui.
La porte de mémoire est là pour empêcher les données d'être stockées (et récupérées) à l'aide d'un registre CPU ou d'une adresse temporaire différente au lieu de l'adresse de la mémoire fournie. Ceci est important lorsque les données de l'adresse de mémoire spécifiée peuvent être affectées par des événements externes, tels que des périphériques matériels ou des processus.
@Davidschwartz J'ai posté une question de suivi à << a href = "http://stackoverflow.com/q/38072450/1175080"> Stackoverflow.com/q/38072450/1175080 >.