web-dev-qa-db-de.com

Mehrfachzuweisung in einer Zeile

Ich stoße gerade auf die Aussage in eingebettetem c (dsPIC33)

sample1 = sample2 = 0;

Würde das bedeuten

sample1 = 0;

sample2 = 0;

Warum tippen sie es so? Ist das eine gute oder schlechte Kodierung?

21
riscy

Denken Sie daran, dass die Zuweisung von rechts nach links erfolgt und dass es sich um normale Ausdrücke handelt. Aus der Sicht des Compilers also die Linie

sample1 = sample2 = 0;

ist das gleiche wie

sample1 = (sample2 = 0);

das ist das gleiche wie

sample2 = 0;
sample1 = sample2;

Das heißt, sample2 wird Null zugewiesen, dann wird sample1 der Wert von sample2 zugewiesen. In der Praxis das Gleiche, als würden Sie beide auf Null setzen, wie Sie vermuteten.

34

Formal für zwei Variablen t und u vom Typ T und U

T t;
U u;

die Zuordnung

t = u = X;

(wobei X ein Wert ist) wird interpretiert als

t = (u = X);

und entspricht einem Paar unabhängiger Zuweisungen

u = X;
t = (U) X;

Beachten Sie, dass der Wert von X die Variable t erreichen soll, "als ob" sie zuerst die Variable u durchlaufen hat, aber es ist nicht erforderlich, dass dies buchstäblich so geschieht. X muss einfach in den Typ u konvertiert werden, bevor sie t zugewiesen wird. Der Wert muss nicht zuerst u zugewiesen und dann von u nach t kopiert werden. Die beiden oben genannten Zuweisungen sind tatsächlich nicht sequenziert und können in beliebiger Reihenfolge ausgeführt werden 

t = (U) X;
u = X;

ist auch ein gültiger Ausführungszeitplan für diesen Ausdruck. (Beachten Sie, dass diese Sequenzierungsfreiheit spezifisch für die Sprache C ist, in der das Ergebnis einer Zuweisung in einem r-Wert angegeben wird. In C++ wird die Zuweisung zu einem l-Wert ausgewertet, der die Sequenzierung von "verketteten" Zuweisungen erfordert.)

Es gibt keine Möglichkeit zu sagen, ob es eine gute oder schlechte Programmierpraxis ist, ohne mehr Kontext zu sehen. In Fällen, in denen die beiden Variablen eng miteinander verbunden sind (wie x und y-Koordinate eines Punkts), ist es durchaus empfehlenswert, sie mithilfe der "verketteten" Zuweisung auf einen gemeinsamen Wert zu setzen. Wenn die Variablen jedoch völlig unabhängig voneinander sind, ist das Mischen in einer einzigen "verketteten" Zuweisung definitiv keine gute Idee. Insbesondere wenn diese Variablen unterschiedliche Typen haben, kann dies zu unbeabsichtigten Folgen führen.

8
AnT

Ich denke, es gibt keine gute Antwort auf C-Sprache ohne tatsächliche Assembly-Auflistung :)

Also für ein einfaches Programm:

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

Ich habe diese Anordnung (Kubuntu, gcc 4.8.2, x86_64) mit der -O0-Option natürlich;

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

Also ist gcc eigentlich das ganze Zeug.

6
hurufu

Die Ergebnisse sind die gleichen. Manche Leute bevorzugen die Verkettung von Zuweisungen, wenn alle denselben Wert haben. Es ist nichts falsch mit diesem Ansatz. Persönlich finde ich dies vorzuziehen, wenn die Variablen eng miteinander verwandte Bedeutungen haben.

1
kjelderg

Sie können selbst entscheiden, dass diese Art der Codierung gut oder schlecht ist.

  1. Sehen Sie sich einfach den Assemblycode für die folgenden Zeilen in Ihrer IDE an.

  2. Ändern Sie dann den Code in zwei separate Zuweisungen und sehen Sie die Unterschiede.

Darüber hinaus können Sie auch versuchen, Optimierungen (sowohl Größen- als auch Geschwindigkeitsoptimierungen) in Ihrem Compiler zu aktivieren/deaktivieren, um zu sehen, wie sich dies auf den Assembly-Code auswirkt.

Zum Codierstil und verschiedenen Codierungsempfehlungen siehe hier: Lesbarkeit a = b = c oder a = c; b = c;

Ich glaube das mit 

sample1 = sample2 = 0;

einige Compiler erzeugen eine Assembly etwas schneller als zwei Zuweisungen:

sample1 = 0;
sample2 = 0;

besonders wenn Sie auf einen Wert ungleich Null initialisieren. Denn die Mehrfachzuweisung wird übersetzt in:

sample2 = 0; 
sample1 = sample2;

Statt zwei Initialisierungen machen Sie also nur eine und eine Kopie. Die Geschwindigkeit (falls vorhanden) wird winzig klein sein, aber im eingebetteten Fall zählt jedes kleine Bit!

0
P. B. M.

Achten Sie auf diesen Sonderfall ... Angenommen, b ist ein Array einer Struktur des Formulars

{
    int foo;
}

und sei ein Versatz in b. Betrachten Sie die Funktion realloc_b (), die ein int zurückgibt und die Neuzuweisung von Array b durchführt. Betrachten Sie diese Mehrfachzuweisung:

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

Nach meiner Erfahrung (b + i) wird zuerst gelöst, sagen wir, es ist b_i im RAM; dann wird realloc_b () ausgeführt. Wenn realloc_b () b im RAM ändert, führt dies dazu, dass b_i nicht mehr zugewiesen wird. 

Die Variable a ist gut zugewiesen, aber (b + i) -> foo ist nicht darauf zurückzuführen, dass b geändert wurde

Dies kann einen Segmentierungsfehler verursachen, da sich b_i möglicherweise an einem nicht zugewiesenen RAM -Ordner befindet.

Um fehlerfrei zu sein und (b + i) -> foo gleich a zu haben, muss die einzeilige Zuweisung in zwei Zuweisungen aufgeteilt werden:

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

bedeutet 

sample1 = 0;
sample2 = 0;  

wenn und nur wenn sample2 vorher deklariert wurde.
Sie können dies nicht tun: 

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

Wie andere gesagt haben, ist die Reihenfolge, in der dies ausgeführt wird, deterministisch. Der Operator-Vorrang des Operators = garantiert, dass dies von rechts nach links ausgeführt wird. Mit anderen Worten, es garantiert, dass sample2 vor sample1 einen Wert erhält.

Jedoch, Mehrfachzuweisungen in einer Zeile sind schlechte Praxis und von vielen Kodierungsstandards (*) verboten. Erstens ist es nicht besonders lesbar (oder Sie würden diese Frage nicht stellen). Zweitens ist es gefährlich. Wenn wir zum Beispiel haben

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

der Operator-Vorrang garantiert dann die gleiche Ausführungsreihenfolge wie zuvor (+ hat einen höheren Rang als =, also die Klammer). sample2 wird vor sample1 ein Wert zugewiesen. Im Gegensatz zur Operatorpriorität ist die Reihenfolge der Auswertung der Operatoren nicht deterministisch, es ist ein unspezifiziertes Verhalten. Wir können nicht wissen, dass der ganz rechte Funktionsaufruf vor dem ganz linken ausgewertet wird.

Der Compiler kann den obigen Code wie folgt in Maschinencode übersetzen:

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

Wenn der Code davon abhängt, dass func () in einer bestimmten Reihenfolge ausgeführt wird, haben wir einen bösen Fehler erstellt. Es funktioniert an einer Stelle des Programms zwar einwandfrei, unterbricht jedoch einen anderen Teil desselben Programms, obwohl der Code identisch ist. Da der Compiler die Möglichkeit hat, Unterausdrücke in beliebiger Reihenfolge auszuwerten.


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

0
Lundin