C ordre d’évaluation de l’affectation

On m’a rencontré dans un cas où le code multi-plateforme se comportait différemment dans une instruction d’affectation de base

Un compilateur a d’abord évalué Lvalue, ensuite Rvalue, puis l’affectation.

Un autre compilateur a fait Rvalue en premier, Lvalue en second, puis l’affectation.

Cela peut avoir un impact si Lvalue influence la valeur de Rvalue comme indiqué dans le cas suivant:

struct MM { int m; } int helper (struct MM** ppmm ) { (*ppmm) = (struct MM *) malloc (sizeof (struct MM)); (*ppmm)->m = 1000; return 100; } int main() { struct MM mm = {500}; struct MM* pmm = &mm pmm->m = helper(&pmm); printf(" %d %d " , mm.m , pmm->m); } 

L’exemple ci-dessus, la ligne pmm->m = helper(&mm); , dépend de l’ordre d’évaluation. Si Lvalue est évaluée en premier, pmm-> m est équivalente à mm.m et si Rvalue calculée en premier, pmm-> m est équivalente à l’instance MM allouée sur le tas.

Ma question est de savoir s’il existe une norme C pour déterminer l’ordre d’évaluation (n’en a trouvé aucune), ou si chaque compilateur peut choisir quoi faire. Existe-t-il d’autres pièges similaires dont je devrais être au courant?

    La sémantique pour l’évaluation d’une expression = inclut celle

    L’effet secondaire de la mise à jour de la valeur stockée de l’opérande gauche est séquencé après les calculs de valeur des opérandes gauche et droit. Les évaluations des opérandes ne sont pas séquencées.

    (C2011, 6.5.16 / 3; italiques ajoutés)

    La disposition soulignée permet explicitement votre différence observée dans le comportement du programme lorsqu’il est compilé par différents compilateurs. De plus, non séquencé signifie, entre autres choses, qu’il est permis que les évaluations se déroulent dans un ordre différent même dans des exécutions différentes de la même construction du programme. Si la fonction dans laquelle apparaissent les évaluations non séquencées était appelée plus d’une fois, il serait alors possible que les évaluations se déroulent dans un ordre différent au cours d’appels différents dans la même exécution du programme.

    Cela répond déjà à la question, mais il est important de voir la situation dans son ensemble. Modifier un object ou appeler une fonction dans ce sens constitue un effet secondaire (C2011, 5.1.2.3/2). Cette disposition clé entre donc en jeu:

    Si un effet secondaire sur un object scalaire n’est pas séquencé par rapport à un effet secondaire différent sur le même object scalaire ou à un calcul de valeur utilisant la valeur du même object scalaire, le comportement est indéfini.

    (C2011, 6.5 / 2)

    La fonction appelée a pour effet secondaire de modifier la valeur stockée dans main() variable pmm main() . pmm évaluation de l’opérande gauche de l’assignation implique un calcul de la valeur utilisant la valeur de pmm , et celles-ci ne sont pas séquencées. indéfini.

    Un comportement indéfini doit être évité à tout prix. Parce que le comportement de votre programme n’est pas défini, il ne se limite pas aux deux alternatives que vous avez observées (au cas où cela ne soit pas assez grave). La norme C n’impose aucune limite à ce qu’elle peut faire. Au lieu de cela, il pourrait planter, mettre à zéro la table de partition de votre disque dur ou, si vous disposez du matériel approprié, invoquer des démons nasaux. Ou n’importe quoi d’autre. La plupart d’entre elles sont peu probables, mais le meilleur sharepoint vue est que si votre programme a un comportement indéfini, votre programme est erroné .

    Lors de l’utilisation de l’opérateur d’affectation simple: = , l’ordre d’évaluation des opérandes n’est pas spécifié. Il n’y a pas non plus de sharepoint séquence entre les évaluations.

    Par exemple si vous avez deux fonctions:

     *Get() = logf(2.0f); 

    Il n’est pas spécifié dans quel ordre ils sont appelés à tout moment, et pourtant ce comportement est complètement défini.

    Un appel de fonction introduira un sharepoint séquence. Cela se produira après l’évaluation des arguments et avant l’appel proprement dit. L’opérateur ; introduira également un sharepoint séquence. Ceci est important car un object ne doit pas être modifié deux fois sans un sharepoint séquence intermédiaire, sinon le comportement n’est pas défini.

    Votre exemple est particulièrement compliqué en raison d’un comportement non spécifié et peut avoir des résultats différents selon que l’opérande gauche ou droit est évalué en premier.

    1. L’opérande de gauche est évalué en premier.

    L’opérande de gauche est évalué et le pointeur pmm pointe vers la structure mm . Ensuite, la fonction est appelée et un sharepoint séquence se produit. il modifie le pointeur pmm en le pointant sur la mémoire allouée, suivie d’un sharepoint séquence en raison de l’opérateur ; . Ensuite, il stocke la valeur 1000 dans le membre m , suivi d’un autre sharepoint séquence en raison de ; . La fonction retourne 100 et l’assigne à l’opérande gauche, mais depuis que l’opérande gauche a été évalué en premier, la valeur 100 est affectée à l’object mm , plus précisément à son membre m .

    mm->m a la valeur 100 et ppm->m a la valeur 1000. Il s’agit du comportement défini, aucun object n’est modifié deux fois entre les points de séquence.

    1. Le bon opérande est évalué en premier.

    La fonction s’appelle d’abord, le sharepoint séquence apparaît, elle modifie le pointeur ppm en le pointant sur la nouvelle structure allouée, suivie d’un sharepoint séquence. Ensuite, il stocke la valeur 1000 dans le membre m , suivi d’un sharepoint séquence. Puis la fonction retourne. Ensuite, l’opérande de gauche est évalué, ppm->m pointe vers la nouvelle structure allouée et son membre m est modifié en lui affectant la valeur 100.

    mm->m aura la valeur 500 puisqu’il n’a jamais été modifié et pmm->m aura la valeur 100. Aucun object n’a été modifié deux fois entre les points de séquence. Le comportement est défini.