web-dev-qa-db-fra.com

Affectation multiple sur une ligne

Je viens de rencontrer la déclaration dans c intégré (dsPIC33)

sample1 = sample2 = 0;

Est-ce que cela veut dire

sample1 = 0;

sample2 = 0;

Pourquoi le tape-t-il de cette façon? Est-ce que c'est bon ou mauvais?

21
riscy

Rappelez-vous que l’affectation se fait de droite à gauche et qu’il s’agit d’expressions normales. Donc, du point de vue des compilateurs, la ligne

sample1 = sample2 = 0;

est le même que

sample1 = (sample2 = 0);

qui est le même que

sample2 = 0;
sample1 = sample2;

En d’autres termes, sample2 se voit attribuer la valeur zéro, puis sample1 se voit attribuer la valeur sample2. En pratique, cela revient à assigner les deux à zéro, comme vous l'avez deviné.

34

Formellement, pour deux variables t et u de type T et U respectivement

T t;
U u;

la tâche

t = u = X;

(où X est une valeur) est interprété comme

t = (u = X);

et est équivalent à une paire d'assignations indépendantes

u = X;
t = (U) X;

Notez que la valeur de X est supposée atteindre la variable t "comme si" elle est passée par la variable u en premier, mais rien n'exige que cela se produise littéralement de cette façon. X doit simplement être converti en type u avant d'être attribué à t. Il n'est pas nécessaire d'affecter la valeur à u en premier, puis de la copier de u à t. Les deux assignations ci-dessus ne sont en réalité pas séquencées et peuvent se produire dans n’importe quel ordre, ce qui signifie que 

t = (U) X;
u = X;

est également un calendrier d'exécution valide pour cette expression. (Notez que cette liberté de séquence est spécifique au langage C, dans lequel le résultat d'une affectation dans une valeur rvalue. En C++, une affectation est évaluée à une valeur lvalue, ce qui nécessite de séquencer des affectations "chaînées".)

Il n'y a aucun moyen de dire s'il s'agit d'une bonne ou d'une mauvaise pratique de programmation sans avoir plus de contexte. Dans les cas où les deux variables sont étroitement liées (telles que x et y coordonnée d'un point), leur attribuer une valeur commune en utilisant une affectation "chaînée" est en fait une bonne pratique (je dirais même "pratique recommandée"). Mais lorsque les variables sont complètement indépendantes les unes des autres, les mélanger dans une seule affectation "chaînée" n'est certainement pas une bonne idée. Surtout si ces variables ont des types différents, ce qui peut avoir des conséquences inattendues.

8
AnT

Je pense qu'il n'y a pas de bonne réponse sur le langage C sans la liste de l'Assemblée proprement dite :)

Donc, pour un programme simpliste:

int main() {
        int a, b, c, d;
        a = b = c = d = 0;
        return a;
}

J'ai cette assemly (Kubuntu, gcc 4.8.2, x86_64) avec l'option -O0 bien sûr;)

main:
        pushq   %rbp
        movq    %rsp, %rbp

        movl    $0, -16(%rbp)       ; d = 0
        movl    -16(%rbp), %eax     ; 

        movl    %eax, -12(%rbp)     ; c = d
        movl    -12(%rbp), %eax     ;

        movl    %eax, -8(%rbp)      ; b = c
        movl    -8(%rbp), %eax      ;

        movl    %eax, -4(%rbp)      ; a = b
        movl    -4(%rbp), %eax      ;

        popq    %rbp

        ret                         ; return %eax, ie. a

Donc, gcc est en fait enchaîner tout ça.

6
hurufu

Les résultats sont les mêmes. Certaines personnes préfèrent enchaîner les affectations si elles ont toutes la même valeur. Il n'y a rien de mal avec cette approche. Personnellement, je trouve cela préférable si les variables ont des significations étroitement liées.

1
kjelderg

Vous pouvez décider vous-même que cette méthode de codage est bonne ou mauvaise.

  1. Il suffit de voir le code d'assemblage pour les lignes suivantes dans votre IDE.

  2. Modifiez ensuite le code en deux assignations distinctes et observez les différences.

En plus de cela, vous pouvez également essayer d'activer/désactiver les optimisations (optimisations de taille et de vitesse) dans votre compilateur pour voir comment cela affecte le code d'assembly.

1

En ce qui concerne le style de codage et les diverses recommandations de codage, voir ici: Lisibilité a = b = c ou a = c; b = c ;?

Je crois qu'en utilisant 

sample1 = sample2 = 0;

certains compilateurs produiront un assemblage légèrement plus rapide que 2 assignations:

sample1 = 0;
sample2 = 0;

spécialement si vous initialisez à une valeur différente de zéro. Parce que l'affectation multiple se traduit par:

sample2 = 0; 
sample1 = sample2;

Ainsi, au lieu de 2 initialisations, vous ne faites qu'une copie et une. La vitesse d'accélération (le cas échéant) sera minime, mais dans le cas intégré, chaque petit geste compte!

0
P. B. M.

Prends soin de ce cas spécial ... suppose que b est un tableau d'une structure de la forme

{
    int foo;
}

et que je sois un décalage en b. Considérons la fonction realloc_b () renvoyant un int et effectuant la réallocation du tableau b. Considérez cette tâche multiple:

a = (b + i) -> truc = realloc_b ();

À mon expérience (b + i) est résolu en premier, disons que c'est b_i dans le RAM; alors realloc_b () est exécuté. Comme realloc_b () change b en RAM, il en résulte que b_i n'est plus alloué. 

La variable a est bien affectée mais (b + i) -> foo ne l’est pas parce que b a été modifié.

Cela peut provoquer une erreur de segmentation car b_i est potentiellement dans un emplacement RAM non alloué.

Pour être exempt de bogues et avoir (b + i) -> toto égal à a, l'affectation d'une ligne doit être scindée en deux affectations:

a = reallocB();
(b + i)->foo = a;
0
Nicolas Rapin
sample1 = sample2 = 0;

veut dire 

sample1 = 0;
sample2 = 0;  

si et seulement si sample2 est déclaré plus tôt.
Vous ne pouvez pas faire de cette façon: 

int sample1 = sample2 = 0; //sample1 must be declared before assigning 0 to it
0
haccks

Comme d'autres l'ont dit, l'ordre dans lequel cela est exécuté est déterministe. Le priorité de l'opérateur de l'opérateur = garantit son exécution de droite à gauche. En d'autres termes, cela garantit à échantillon2 une valeur avant échantillon1.

Cependant, plusieurs assignations sur une ligne sont une mauvaise pratique et sont interdites par de nombreuses normes de codage (*). Tout d'abord, il n'est pas particulièrement lisible (sinon vous ne poseriez pas cette question). Deuxièmement, c'est dangereux. Si on a par exemple

sample1 = func() + (sample2 = func());

alors la priorité de l'opérateur garantit le même ordre d'exécution qu'auparavant (+ a une priorité supérieure à =, donc la parenthèse). sample2 recevra une valeur avant sample1. Mais contrairement à la priorité des opérateurs, le ordre d'évaluation des opérateurs n'est pas déterministe, il s'agit d'un comportement non spécifié. Nous ne pouvons pas savoir que l'appel de fonction le plus à droite est évalué avant l'appel le plus à gauche.

Le compilateur est libre de traduire ce qui précède en code machine comme ceci:

int tmp1 = func();
int tmp2 = func();
sample2 = tmp2;
sample1 = tmp1 + tmp2;

Si le code dépend de l'exécution de func () dans un ordre particulier, nous avons créé un bogue méchant. Cela peut fonctionner correctement à un endroit du programme, mais insérer une autre partie du même programme, même si le code est identique. Parce que le compilateur est libre d'évaluer les sous-expressions dans l'ordre de son choix.


(*) MISRA-C: 2004 12.2, MISRA-C: 2012 13.4, CERT-C EXP10-C.

0
Lundin