Forum: Compiler & IDEs Priorität von Operatoren


von Benjamin (Gast)


Lesenswert?

Ich habe mal folgenden Code-Schnipsel zur Priorität von Operatoren:
1
unsigned char tst_int2 = 7;
2
unsigned char tst_int = 4;
3
unsigned char *tst_ptr = &tst_int;
4
5
printf("address: %p\tvalue: %d\n", tst_ptr, *tst_ptr);
6
*tst_ptr++; // first increment address, then dereference it
7
printf("address: %p\tvalue: %d\n", tst_ptr, *tst_ptr);

ergibt folgende Ausgabe:

address: 0022FF46       value: 4
address: 0022FF47       value: 7

Hier wird zuerst die Adresse inkrementiert und danach erst referenziert.
1
printf("%X\n", tst_int);
2
tst_int = ~tst_int++; //first invert value, then increment it
3
printf("%X\n", tst_int);

ergibt folgende Ausgabe:

4
FC

Also wird zuerst invertiert und danach inkrementiert.

Wenn ich mir die Tabelle der Prioritäten auf 
http://openbook.galileocomputing.de/c_von_a_bis_z/029_c_anhang_a_001.htm#mjec9bf1c5e3fe2e46047c2f9c7aa9c531 
anschaue, verstehe ich gar nichts mehr. Wie soll das denn 
zusammenpassen? Wieso wird im ersten Fall vorher inkrementiert, im 
2.Fall aber erst hinterher?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Benjamin schrieb:

> *tst_ptr++; // first increment address, then dereference it

Mit ausreichenden Warnungs-Einstellungen sollte dir der Compiler
hier sagen, dass dieser Stern nutzlos ist: das Ergebnis des
Dereferenzierens des Zeigers wird verworfen.

> tst_int = ~tst_int++; //first invert value, then increment it

Undefined behaviour.  (Standard § 6.5, Absatz 2)

von Benjamin (Gast)


Lesenswert?

Vielen Dank für Deine Antwort. Damit hätte ich jetzt nicht gerechnet, 
diese Sprache macht mich noch fertig...

Mit -Wall habe ich die Warnung für Nummer 2 jetzt bekommen.

Das erste wird bei mir aber doch nicht verworfen, sondern er 
dereferenziert den zeiger doch, nachdem er inkrementiert wurde?

von Oliver S. (oliverso)


Lesenswert?

Mit dem dereferenzierten Wert passiert dann aber nichts.

Oliver

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

1
$ cc -O -Wall -Wextra -c foo.c
2
foo.c: In Funktion »main«:
3
foo.c:10:1: Warnung: berechneter Wert ist unbenutzt [-Wunused-value]
4
foo.c:14:9: Warnung: Operation auf »tst_int« könnte undefiniert sein [-Wsequence-point]
5
foo.c:5:15: Warnung: Variable »tst_int2« wird nicht verwendet [-Wunused-variable]

Benjamin schrieb:
> Damit hätte ich jetzt nicht gerechnet, diese Sprache macht mich noch
> fertig...

Naja, Prä- und Postfix-Inkrement oder -Dekrement wirken immer
irgendwann vor bzw. nach der gesamten Berechnung des Ausdrucks.
Der Standard lässt aber offen, in welcher Reihenfolge der Compiler
die einzelnen Bestandteile der Zuweisungsanweisung bewertet, d. h.
sicher, dass der Zeiger (infolge des ++-Operators) auch tatsächlich
erhöht worden ist, kann man sich erst nach der Zuweisungsanweisung
sein.  Ob der Zeiger vor der Zuweisung selbst oder erst danach
inkrementiert wird, ist eben nicht definiert.

Es ist daher grundsätzlich eine schlechte Idee, irgendetwas, das auf
der rechten Seite einer Zuweisung einen Prä- oder Postfix-Operator
hat, ebenfalls nochmal auf der linke Seite der Zuweisung stehen zu
haben.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Benjamin schrieb:
> Das erste wird bei mir aber doch nicht verworfen, sondern er
> dereferenziert den zeiger doch, nachdem er inkrementiert wurde?

Oliver S. schrieb:
> Mit dem dereferenzierten Wert passiert dann aber nichts.

Es gibt übrigens einen Fall, bei dem man sowas trotzdem zuweilen
gebrauchen kann: wenn der Zeiger auf ein Objekt zeigt, welches als
“volatile” markiert ist.  Das kann dann beispielsweise ein IO-Register
eines Controllers sein, und bei dem ist es schon wichtig, dass es
auch wirklich gelesen wird, denn das Lesen kann ja in der Hardware
Seiteneffekte haben (bspw. das Rücksetzen einer Interruptbedingung).

Wenn man sowas wirklich braucht, dann sollte man das aber auch
explizit hinschreiben:
1
  (void)*udr_pointer;  // read UDR to clear any pending interrupt

Der Typecast nach void macht dabei klar, dass es die volle Absicht
des Programmierers war, den Wert zu verwerfen.

von Benjamin (Gast)


Lesenswert?

>Es ist daher grundsätzlich eine schlechte Idee, irgendetwas, das auf
>der rechten Seite einer Zuweisung einen Prä- oder Postfix-Operator
>hat, ebenfalls nochmal auf der linke Seite der Zuweisung stehen zu
>haben.

Stimmt, das kommt mir wieder bekannt vor. Nochmal danke für die 
ausführliche Antwort.

von DirkB (Gast)


Lesenswert?

Benjamin schrieb:
> Das erste wird bei mir aber doch nicht verworfen, sondern er
> dereferenziert den zeiger doch, nachdem er inkrementiert wurde?

Nein. Es ist das Postinkrement.
Es wird erst dereferenziert und dann wird der Zeiger inkrementiert. (der 
Zeiger, nicht der Wert)

von Oliver S. (oliverso)


Lesenswert?

DirkB schrieb:
> Es wird erst dereferenziert und dann wird der Zeiger inkrementiert. (der
> Zeiger, nicht der Wert)

Du kannst nur sicher sein, daß der nicht-inkrementierte Zeiger 
dereferenziert wird. Genaue Ausführung und Reihenfolge von 
Inkremetierung und Dereferenzierung sind nicht definiert. Genau deshalb 
ist
>i = i++;
undefined behaviour.

Oliver

von Peter D. (peda)


Lesenswert?

Oliver S. schrieb:
> ist
>>i = i++;
> undefined behaviour.

Man kann aber schreiben:
1
i = 0 ? 0 : i++;

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:
> Man kann aber schreiben:
> i = 0 ? 0 : i++;

Nein. Zwischen i und i++ liegt kein sequence point. Der liegt zwischen 
der ersten 0 und dem Rest.

Zulässig wäre beispielsweise
  i = i++ ? i : 0;

von DirkB (Gast)


Lesenswert?

Ich bezog mich auf das
1
*tst_ptr++; // first increment address, then dereference it
Der Code ist Postinkrement.
Im Kommentar steht aber die Beschreibung zum Präinkrement.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Zulässig wäre beispielsweise
>   i = i++ ? i : 0;

Allerdings eher als Beitrag zum IOCCC denn in vernünftigem Code. ;-)

von Rolf Magnus (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Wenn man sowas wirklich braucht, dann sollte man das aber auch
> explizit hinschreiben:
>   (void)*udr_pointer;  // read UDR to clear any pending interrupt
>
> Der Typecast nach void macht dabei klar, dass es die volle Absicht
> des Programmierers war, den Wert zu verwerfen.

Hmm, in meinem Fall würde es eher davon ablenken. Ich würde mich halt 
fragen, wozu dieser unsinnige Cast da sein soll, statt warum das 
Ergebnis der Dereferenzierung verworfen wird. ;-)

von DirkZ (Gast)


Lesenswert?

Benjamin schrieb:
> unsigned char tst_int2 = 7;
> unsigned char tst_int = 4;
> unsigned char *tst_ptr = &tst_int;
>
> printf("address: %p\tvalue: %d\n", tst_ptr, *tst_ptr);
> *tst_ptr++; // first increment address, then dereference it
> printf("address: %p\tvalue: %d\n", tst_ptr, *tst_ptr);

Die Anweisung *tst_ptr++ inkrementiert die Speicherstelle! Das die Zahl 
7 dabei rauskommt ist Glück. Ist zufällig der Inhalt von tst_int2.

Wenn Du den Inhalt von tst_int ändern möchtest, musst Du klammern: 
(*tst_ptr)++

Ergibt dann 5.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.