* C++ == C++ *

Moderator #4712914
Lesenswert?

Da meckert schon der Compiler bzw. Linker:
1
implicit declaration of function ‘print’
2

3
undefined reference to `print'

Ersetzt ma  print durch printf und inkludiert stdio.h, bleibt immer noch
eine Warnung
1
‘C’ is used uninitialized in this function

Selbst wenn man C intialisiert, meckert der Compiler:
1
operation on ‘C’ may be undefined

Der Ausdruck C++ == C++ ist nämlich gar nicht erlaubt, da er
undefiniertes Verhalten bewirkt.

Diese Diskussion um undefiniertes Verhalten – insbesondere im
Zusammenhang mit den Operatoren ++ und --, hatten wir hier aber schon
oft.


PS: Der Clang-Compiler erläutert das Problem noch etwas dataillierter:
1
warning: multiple unsequenced modifications to 'C' [-Wunsequenced]
2
  if ( C++ == C++ ) {
3
        ^      ~~

Es sind also tatsächlich die beiden ++, die sich gegenseitig beißen.
#4712937
Lesenswert?

Dass der Compiler da meckert ist ja klar. Das sollte eher eine kleine 
Denkaufgabe sein , die sich als gar nicht mal so einfach herausstellt.
Ich nehme mal an C wird einfach irgendwo im Speicher festgelegt und hat 
halt einen bestimmten wert. Man muss sich nun ziemlich gut mit C 
auskennen um genau sagen zu können wie und wann welche Befehle 
ausgeführt werden.

Mein Output :

A is not A
Gast #4712941
Lesenswert?

Maximilian S. schrieb:
> Man muss sich nun ziemlich gut mit C
> auskennen um genau sagen zu können wie und wann welche Befehle
> ausgeführt werden.

das hat aber wenig mit C zu tun. Man muss maximal seinen Compiler 
kennen. Denn hier kann jeder Compiler etwas anderes liefern. (sogar jede 
Version vom Compiler)
Gast #4712955
Lesenswert?

Weil man eine Variable nur einmal pro Sequencepoint zuweisen darf. Ein 
Sequencepoint ist der Punkt ab dem eine Zuweisung zu einer Variablen für 
den Rest des Programm sichtbar wird. Beispielsweise ist jedes ';' ein 
solcher.
Moderator #4712965
Lesenswert?

Peter II schrieb:
> Denn hier kann jeder Compiler etwas anderes liefern. (sogar jede
> Version vom Compiler)

Und sogar ein und der selbe Compiler in unterschiedlichen
Optimierungsstufen.

Es ist nicht einmal garantiert, dass das Programm überhaupt etwas
ausgibt. Es könnte (zumindest theoretisch) sogar passieren, dass bei der
Ausführung des Programms die Festplatte komplett gelöscht wird :)
Selbst in diesem Fall wäre der Compiler immer noch ISO-konform.
Gast #4712967
Lesenswert?

na dann nächste runde Yalu
und auf die festplatte aufpassen!
;-)

1
int main( int c, char *v[]) {
2
 
3
  struct {
4
   unsigned long x : 33;
5
  } s;
6

7
  typedef typeof(0+s.x) uint33;
8
  
9
  uint33 y = -1;
10
  
11
  printf("%lu\n\n", (unsigned long)y);
12
  
13
  uint33 t[3] = { 0x100000000 };
14
  t[1] = t[0] + 1;
15
  t[2] = t[0] + t[1];
16
  
17
  for (int i = 0; i<3; i++)
18
    printf("%9lx\n", (unsigned long)t[i]);
19
  
20
}

gcc t.c && ./a.outp

??
Moderator Persönliche Seite #4712991
Lesenswert?

freitagsposter schrieb:
> gcc t.c && ./a.out
1
$ gcc t.c  && ./a.out
2
t.c: In function 'main':
3
t.c:6:4: error: width of 'x' exceeds its type
4
t.c:15:3: warning: large integer implicitly truncated to unsigned type [-Woverflow]
5
t.c:19:3: error: 'for' loop initial declarations are only allowed in C99 mode
6
t.c:19:3: note: use option -std=c99 or -std=gnu99 to compile your code
1
$ gcc -std=c99 t.c && ./a.out
2
t.c: In function 'main':
3
t.c:6:4: error: width of 'x' exceeds its type
4
t.c:9:18: error: expected declaration specifiers or '...' before numeric constant
5
t.c:11:3: error: unknown type name 'uint33'
6
t.c:15:3: error: unknown type name 'uint33'
7
t.c:15:3: warning: overflow in implicit constant conversion [-Woverflow]

Außerdem hast Du ein

#include <stdio.h>

vergessen ;-)
Gast #4713001
Lesenswert?

Frank M. schrieb:

> freitagsposter schrieb:
>> gcc t.c && ./a.out$ gcc t.c  && ./a.out
> t.c: In function 'main':
> t.c:6:4: error: width of 'x' exceeds its type
> t.c:15:3: warning: large integer implicitly truncated to unsigned type
> [-Woverflow]
> t.c:19:3: error: 'for' loop initial declarations are only allowed in C99
> mode
> t.c:19:3: note: use option -std=c99 or -std=gnu99 to compile your code
> $ gcc -std=c99 t.c && ./a.out
> t.c: In function 'main':
> t.c:6:4: error: width of 'x' exceeds its type
> t.c:9:18: error: expected declaration specifiers or '...' before numeric
> constant
> t.c:11:3: error: unknown type name 'uint33'
> t.c:15:3: error: unknown type name 'uint33'
> t.c:15:3: warning: overflow in implicit constant conversion [-Woverflow]


dann taugt dein compiler (einstellung) nicht für ein 33 bit integer type
Gast #4713035
Lesenswert?

Frank M. schrieb:
> t.c: In function 'main':
> t.c:6:4: error: width of 'x' exceeds its type
> ...
1
.LC0:
2
        .string "%lu %lu\n\n"
3
.LC1:
4
        .string "%9lx\n"
5
main:
6
        subq    $8, %rsp
7
        movabsq $8589934590, %rdx
8
        movabsq $8589934591, %rsi
9
        movl    $.LC0, %edi
10
        xorl    %eax, %eax
11
        call    printf
12
        movabsq $4294967296, %rsi
13
        movl    $.LC1, %edi
14
        xorl    %eax, %eax
15
        call    printf
16
        movabsq $4294967297, %rsi
17
        movl    $.LC1, %edi
18
        xorl    %eax, %eax
19
        call    printf
20
        movl    $1, %esi
21
        movl    $.LC1, %edi
22
        xorl    %eax, %eax
23
        call    printf
24
        xorl    %eax, %eax
25
        addq    $8, %rsp
26
        ret

x86-64 gcc 6.1
-xc -O2
Gast #4713051
Lesenswert?

und jetzt?
1
// dont forget this!!
2
// ;-)
3
#include <stdio.h>
4

5
int main(int c, char *v[])
6
{
7
  struct {
8
    unsigned long x : 33;
9
  } s;
10

11
  typedef typeof(0+s.x) uint33;
12

13
  uint33 y = -1;
14

15
  printf("%lu %lu\n\n", (unsigned long)y, (unsigned long)(y + y));
16

17
  uint33 t[3] = { 0x100000000 };
18
  t[1] = t[0] + 1;
19
  t[2] = t[0] + t[1];
20

21
  for (int i = 0; i<3; i++)
22
    printf("%9lx\n", (unsigned long)t[i]);
23
  
24
}
Moderator #4713225
Lesenswert?

freitagsposter schrieb:
> na dann nächste runde Yalu
> und auf die festplatte aufpassen!
> ;-)

Ja, das ist schon interessanter :)

Dazu muss erst einmal gesagt werden, dass der typeof-Operator kein
ISO-C, sondern eine GNU-Erweiterung ist.

Auch unsigned-long-Bitfelder sind Erweiterungen, die allerdings in der
ISO-Norm explizit erlaubt werden.

Leider kann ich in der Norm auf die Schnelle keine Informationen darüber
finden, welchen Typ einzelne Bitfelder haben, wenn sie bspw. in
arithmetischen Ausdrücken verwendet werden. Bei Bitfeldern mit
erweiterten Datentypen (im vorliegenden Fall unsigned long int) scheint
der GCC mit Integer-Typen mit "krummen" Bitbreiten zu arbeiten (hier mit
"unsigned long int:33"). Im Gegensatz dazu haben beim Clang diese
Bitfelder den Typ unsigned long int, weswegen das Verhalten der beiden
Compiler an dieser Stelle unterschiedlich ist. Folgender Code

1
#include <stdio.h>
2

3
int main(void) {
4
 
5
  struct {
6
   unsigned long x : 33;
7
  } s = { 0x1ffffffff };
8

9
  printf("%9lx\n", (unsigned long)s.x);
10
  printf("%9lx\n", (unsigned long)(s.x + 1));
11
  printf("%9lx\n", (unsigned long)(2*s.x));
12
}

liefert mit dem GCC:

1
1ffffffff
2
        0
3
1fffffffe

Die Ergebnisse werden also auf 33 Bit gestutzt.

Beim Clang ist das nicht der Fall, da hier für die Berechnungen
offensichtlich die volle Breite von 64 Bit genutzt wird:

1
1ffffffff
2
200000000
3
3fffffffe

Welcher der beiden Compiler nun recht hat, kann ich nicht sagen.
Möglicherweise beide, da es hier ja um eine C-Erweiterung geht, bei
denen die Norm den Compilern üblicherweise viel Freiheit lässt.
Vielleicht findet aber auch jemand die Stelle in der Norm, wo solche
Dinge genauer geregelt sind.

Was das typeof betrifft: Witzigerweise kann man – wie in deinem Beispiel
gezeigt – über dem Umweg über ein Bitfeld in einer Struktur und typeof
auch ganz gewöhnliche Variablen als Bitfeld deklarieren, so dass bei der
Arthmetik mit solchen Variablen die Ergebnisse ebenfalls auf eine krumme
Bitzahl beschnitten werden.

Das Ganze betrifft aber wohlbemerkt nur Bitfelder, die größer als int
und damit C-Erweiterungen sind. Ein int-Bitfeld wird – unabhängig von
der tatsächlichen Bitzahl – in Ausdrücken immer auf ein int erweitert.
Somit sollten sich die beiden Compiler in diesem Fall gleich verhalten.
#4714804
Lesenswert?

Es ist müßig, eine "Denksportaufabe" zu undefiniertem Verhalten zu 
machen, denn: Sobald das ins Spiel kommt, ist jedes beliebige Ergebnis 
richtig. Es wäre sogar korrekt, wenn das Programm bei dieser if-Abfrage 
beide Zweige (also sowohl if, als auch else) ausführt, oder wenn es 
abstürzt oder eine unfreundliche E-Mail an deinen Chef sendet. In 
comp.lang.c wurden als Beispiel immer Dämonen, die aus deiner Nase 
kommen, genannt. Auch die wären nach ISO-C ein korrektes Ergebnis. ;-)
Moderator Persönliche Seite #4718522
Lesenswert?

Günter X. schrieb:
> Erst wird verglichen, dann wird erhöht, zweimal.

Nein. Genau das ist eben nirgends so festgelegt.

Es ist nur festgelegt, dass nach dem Vergleich C um zwei erhöht
worden sein muss (naja, mal von der fehlenden Initialisierung
abgesehen, die natürlich auch zu undefiniertem Verhalten führt –
insbesondere dann, wenn C beispielsweise vorher zufällig MAX_INT
war).
Gast #4719417
Lesenswert?

Jörg W. schrieb:
> Günter X. schrieb:
>> Erst wird verglichen, dann wird erhöht, zweimal.
>
> Nein. Genau das ist eben nirgends so festgelegt.
>
> Es ist nur festgelegt, dass nach dem Vergleich C um zwei erhöht
> worden sein muss (naja, mal von der fehlenden Initialisierung
> abgesehen, die natürlich auch zu undefiniertem Verhalten führt –
> insbesondere dann, wenn C beispielsweise vorher zufällig MAX_INT
> war).

Was NACH dem Vergleich passiert ist sowieso uninteressant, weil der 
Zustand von C nirgendwo im Programmchen abgefragt wird. BEIM Vergleich, 
VOR den beiden post increments, ist C auf jedem Fall gleich C.

Die Antwort lautet daher "A is A".
Gast #4720494
Lesenswert?

Jörg W. schrieb:
> dulnik schrieb:
>> Die Antwort lautet daher "A is A".
>
> Noch einer, der's nicht kapieren will …

OK, ich habe zu schnell aus der Hüfte geschossen :-) Aber woran liegt 
das genau? Ich dachte die post inkrement operatoren werden nach dem 
Vergleich ausgeführt? Ist der Vergleich keine Expression im Sinne der 
Increment Funktionalität?
Moderator Persönliche Seite #4720534
Lesenswert?

dulnik schrieb:

> OK, ich habe zu schnell aus der Hüfte geschossen :-) Aber woran liegt
> das genau?

Daran, dass du eben innerhalb einer Anweisung nur maximal eine
Veränderung an einem Objekt durchführen darfst, egal ob nun durch
Zuweisung, pre- oder post-In- oder Dekrement-Operatoren.

Alles andere bewirkt gemäß Standard undefiniertes Verhalten.

Der Hintergrund ist einfach, dass der Standard die Compilerhersteller
nicht zu stark in ihrem Optimierungspotenzial bremsen möchte und
ihnen so genügend Freiraum gibt, die einzelnen Teiloperationen
beliebig in der Reihenfolge anzordnen.

> Ich dachte die post inkrement operatoren werden nach dem
> Vergleich ausgeführt?

Genau das ist eben nicht festgelegt.  Es ist nur festgelegt, dass
der Vergleich den Wert von C von vor der Inkrementierung benutzen
muss, aber das sagt nichts darüber aus, ob C zu dem Zeitpunkt, da der
Vergleich stattfindet, bereits inkrementiert worden ist oder nicht.
#4721626
Lesenswert?

Also ich hab's mit gnu getestet und "A is A" (was auch zu erwarten war)

Allerdings habe ich den Fehler der fehlenden Initialisierung 
kompensiert, sowas macht kein ersthafter Programmiere.

Zudem steht in allmöglichen C Büchern die Auswirkung der Schreibweise 
++i oder i++

Wer das irgnoriert sollte sich nicht ärgern wenn er Stunden beim 
sinnlosen debuggen verplämpert oder Stress vom Chef bekommt.
#4721644
Lesenswert?

Günter X. schrieb:
> Also ich hab's mit gnu getestet und "A is A" (was auch zu erwarten war)

Ja und ich mit gcc (Gentoo 4.9.3 p1.4, pie-0.6.4) 4.9.3:
1
abd@basalt ~ $ gcc -x c -o ud -
2
#include <stdio.h>
3
#define print puts
4
void main (void) {
5
int C=0;
6
 if ( C++ == C++ ) {
7
  print ( "A is A\n" );
8
  } else {
9
  print ( "A is not A\n");
10
 }
11
}
12
abd@basalt ~ $ ./ud 
13
A is not A
14

15
abd@basalt ~ $

Undefined eben.
Gast #4721654
Lesenswert?

freitagsposter schrieb:
> viel spass beim freitagsabtausch
Die erste Frage ist, welche Sprache das im Beispiel ist.
C oder C++ ist es wegen "void main" nicht, C# und Java haben meines 
Wissens kein print, Assembler, Fortran, Pascal, Cobol und so weiter ist 
es offensichtlich nicht…
Also vor der Diskussion über den Standard, erstmal die Diskussion über 
die Sprache. [Hier fehlt mir jetzt doch ein passendes Smiley.]
#4721725
Lesenswert?

Jörg W. schrieb:
> Es ist nur festgelegt, dass
> der Vergleich den Wert von C von vor der Inkrementierung benutzen
> muss, aber das sagt nichts darüber aus, ob C zu dem Zeitpunkt, da der
> Vergleich stattfindet, bereits inkrementiert worden ist oder nicht.

Der erste Teil beschreibt klar die Regel welche im zweiten Teil wieder 
in Frage gestellt wird. Der Satz als solches ist nicht logisch.


Ist das eine persönliche Interpretation oder gibt es für den Satz 
belastbare Beweise?
#4721731
Lesenswert?

Frank M. schrieb:
> gcc 4.6.1: A is A
> gcc 4.7.4: A is A
> gcc 4.9.2: A is not A

und jetzt bitte noch jerweils mit unterschiedlichen optimierungs 
optionen.
Und selbst wenn:
Das Verhalten ist undefined! sprich, wenn du zwei mal an verschiednen 
Stellen diese operation durchführst, muss nicht beide male das gleiche 
Ergebnis rauskommen. Das kann völlig davon abhängen, wie der Compiler 
den Code drum herum aufbaut.

Wäre es "Implementation Defined" müsste es sich immer gleich verhalten 
und dokumentiert sein, aber bei "undefined" ist es "Zufall"
Moderator Persönliche Seite #4721765
Lesenswert?

Vlad T. schrieb:
> und jetzt bitte noch jerweils mit unterschiedlichen optimierungs
> optionen.

Bringt nix.

> Und selbst wenn:
> Das Verhalten ist undefined! sprich, wenn du zwei mal an verschiednen
> Stellen diese operation durchführst, muss nicht beide male das gleiche
> Ergebnis rauskommen. Das kann völlig davon abhängen, wie der Compiler
> den Code drum herum aufbaut.

Vlad, ich weiß das. Ich wollte eigentlich nur darstellen, wie unsinnig 
eine Deutung des Ergebnisses ist. Deshalb schrieb ich "wer rechter hat".
#4722170
Lesenswert?

Ich kann damit leben das C vor der Abfrage = 0 ist, "A is A" raus kommt 
und am Ende in C 2 zu finden ist, das ergibt Sinn.

Wer meint das anders sehen zu wollen kann das gerne machen.

Das ein solcher Konstrukt in der Praxis nicht wirklich vorkommt 
entschärft die Sache dann komplett :)
#4722190
Lesenswert?

Günter X. schrieb:
> Ist das eine persönliche Interpretation oder gibt es für den Satz
> belastbare Beweise?

Da das in den Standard selbst recht verschlüsselt ausgedrückt ist, hilft 
das C99 Rationale vom Standardisierungsgremium:

"The storage assignment need not take place until the next sequence 
point (*). As a consequence, a straightforward syntactic test for 
ambiguous expressions can be stated. Some definitions: A side effect is 
a storage to any data object [...]. An ambiguous expression is one whose 
value depends upon the order in which side effects are evaluated. [...].

We can then say that an unsequenced expression may be ambiguous [...] if 
more than one operand contains an lvalue referencing the same object and 
one or more operands specify a side-effect to that object."

Der Fall ist hier gegeben.

*: Gibts innerhalb von "C++ == C++" nicht. Appendix C in C99/C11.
#4722216
Lesenswert?

PS: Es ist nicht nur offen, welcher Zweig ausgeführt wird. Es ist auch 
offen, ob C hinterher um 2 oder um 1 grösser ist.

Man erhält bei "C++ == C++" beispielhaft drei Abläufe:
  (1) load T1, copy, increment, store - vom linken C++
  (2) load T2, copy, increment, store - vom rechten C++
  (3) compare T1 and T2
(nochmal: das ist nur ein Beispiel für mögliche Abläufe).

Festgelegt ist hier nur, dass (3) nach dem Load in (1) und (2) 
stattfindet. Ansonsten darf der Compiler die Teiloperationen der 3 
Abläufe beliebig ineinander verzahnen.
Moderator Persönliche Seite #4724479
Lesenswert?

Egal, wie viele Varianten du dir davon ausdenkst: sie erzeugen alle
undefiniertes Verhalten.

Noch was: keiner zwingt dich, beim Absenden deiner Postings eine
Mailadresse einzutragen.  Wenn du dich aber schon entscheidest, eine
einzutragen, dann bitte eine, unter der dich die Forensoftware auch
erreichen kann.  Durch Angabe einer nicht existierenden Adresse
outest du dich einfach nur als Troll.
Gast #4724532
Lesenswert?

Jörg W. schrieb:
> Egal, wie viele Varianten du dir davon ausdenkst: sie erzeugen
> alle
> undefiniertes Verhalten.

dann definier sie dir doch,
es kommt das "selbe" ergebnis hervor.

>
> Noch was: keiner zwingt dich, beim Absenden deiner Postings eine
> Mailadresse einzutragen.  Wenn du dich aber schon entscheidest, eine
> einzutragen, dann bitte eine, unter der dich die Forensoftware auch
> erreichen kann.  Durch Angabe einer nicht existierenden Adresse
> outest du dich einfach nur als Troll.

du kennst doch meine email addresse

;-)
Gast #4724542
Lesenswert?

Jörg W. schrieb:
> Günter X. schrieb:
>> Der Satz als solches ist nicht logisch.
>
> Für dich vielleicht nicht, für mich schon.

für mich auch aber ich verstehe dein "aufkochen" an dem thread nicht 
jörg.

es sollte eine "denksportaufgabe" sein, nichts weiter.
dass jemand hier tatsächlich ein undefiniertes verhalten nachrechnet,
wäre mir nie in den sinn. es geht um die feststellung, dass dies nicht 
im standard definiert ist.

undefiniertes "verhalten"

;-)
#4724612
Lesenswert?

Andreas B. schrieb:
> Sobald undefiniertes Verhalten ins Spiel kommt gibt es keine falschen
> Antworten mehr. Als "Denksport" ist das nicht sehr herausfordernd.

Wie ich ja schon vor über einer Woche in 
Beitrag "Re: * C++ == C++ *" geschrieben habe. 
Interessant, dass hier immer noch darüber diskutiert wird. Aber 
scheinbar ist es für manche doch mit einer größeren gedanklichen 
Anstrengung verbunden, zu erkennen, was undefiniert bedeutet.
Gast #4724619
Lesenswert?

Rolf M. schrieb:
> Aber scheinbar ist es für manche doch mit einer größeren gedanklichen
> Anstrengung verbunden, zu erkennen, was undefiniert bedeutet.

Ach für mich ist "undefiniertes Verhalten" ganz einfach, ich sehe das 
jeden Morgen: ab dem Zeitpunkt wenn meine Frau aufsteht bis zu ihrem 
ersten Kaffee ist das alles "undefiniertes Verhalten". Da lass ich auch 
lieber meine Finger davon. ;-)
#4725745
Lesenswert?

Es ist doch alles ganz einfach:
Wird ein Ausdruck auf der linken Seite modifiziert, darf vor einem 
sequence point nicht ein weiteres mal davon gelesen werden.
Der Compiler warnt einen dann:
1
  j = i++ + i;
test.c:6: warning: operation on 'i' may be undefined
Erlaubt ist z.B.:
1
  j = i++ ? ++i : 10;

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren