Forum: Compiler & IDEs GCC: In welcher Reihenfolge wird if ausgewertet


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Gegeben is folgender Code:
1
bool Bar(void)
2
{
3
  if( Foo1() && Foo2() &&
4
      Foo3() && Foo4()  )
5
  {
6
    return true;
7
  }
8
  else
9
    return false;
10
}
In welcher Reihenfolge wertet der GCC (Version 4.9.3) das aus?

Gruesse

von Bauteiltöter (Gast)


Lesenswert?

Von links nach Rechts. Das hat auch nichts mit dem GCC zu tun, das ist 
im C-Standard festgelegt. Stichwort shortcut-evaluation.
Wenn foo1 false ist wird der Rest nicht mehr aufgerufen, wenn dann foo2 
false ist wird abgebroche u.s.w. das heißt foo4 wird nur aufgerufen wenn 
foo1, foo2 und foo3 true ergeben.

von Pete K. (pete77)


Lesenswert?

Tja, ist nur blöd, wenn in Foo2-4() auch noch etwas aufgerufen wird, was 
erledigt werden soll.

Dann lieber
 ret1=Foo1();
 ret2=Foo2();
 ret3=Foo3();
 ret4=Foo4();
 if (ret1 && ret2 && ret3 && ret4) ...

von Oliver S. (oliverso)


Lesenswert?

Pete K. schrieb:
> Tja, ist nur blöd, wenn in Foo2-4() auch noch etwas aufgerufen
> wird, was
> erledigt werden soll.

Da ist dann nicht blöd, sondern schlicht ein Programmierfehler.

Oliver

von Pete K. (pete77)


Lesenswert?

So meinte ich das auch :-)

von Dr. Sommer (Gast)


Lesenswert?

Pete K. schrieb:
> Dann lieber
>  ret1=Foo1();
>  ret2=Foo2();
>  ret3=Foo3();
>  ret4=Foo4();
>  if (ret1 && ret2 && ret3 && ret4) ...
Oder ganz einfach:
1
if (Foo1 () & Foo2 () & Foo3 () & Foo4 ())
So werden immer alle aufgerufen.

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


Lesenswert?

Dr. Sommer schrieb:
> So werden immer alle aufgerufen.

Aber nicht korrekt ausgewertet.  Wenn Foo1() eine 1 zurückgibt,
Foo2() eine 2, Foo3() eine 4 und Foo4() eine 8, dann ist das
Ergebnis des bitweisen UND eine 0, obwohl das Boolesche UND ein
True gegeben hätte.

von Dr. Sommer (Gast)


Lesenswert?

Jörg W. schrieb:
> Aber nicht korrekt ausgewertet.  Wenn Foo1() eine 1 zurückgibt,
> Foo2() eine 2, Foo3() eine 4 und Foo4() eine 8
Ja stimmt, ich war jetzt von C++ - "bool" Rückgabetypen ausgegangen, da 
gibts nur "true" und "false".

von Quellkothistamin (Gast)


Lesenswert?

bei solchen IFs rollt es mir immer die Zehennägel auf.
Lieber so (IF nicht nötig):
1
{
2
  :  // Dekl. v. ret1 ... ret4
3
  :
4
5
  ret1=Foo1();
6
  ret2=Foo2();
7
  ret3=Foo3();
8
  ret4=Foo4();
9
  return (ret1 && ret2 && ret3 && ret4);
10
}

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


Lesenswert?

Dr. Sommer schrieb:
> Ja stimmt, ich war jetzt von C++ - "bool" Rückgabetypen ausgegangen, da
> gibts nur "true" und "false".

Gibt's bei C auch, aber ohne die Deklaration der Funktionen zu
sehen, kann man nicht sagen, was es genau ist.

Mit && ist es egal.

von Dr. Sommer (Gast)


Lesenswert?

Quellkothistamin schrieb:
> return (ret1 && ret2 && ret3 && ret4);
Und die Klammern sind auch nicht nötig.

Jörg W. schrieb:
> Gibt's bei C auch, aber ohne die Deklaration der Funktionen zu
> sehen, kann man nicht sagen, was es genau ist.
Und dem kann man nicht versehentlich auch eine Zahl außer 0 oder 1 
zuweisen?

Jörg W. schrieb:
> Mit && ist es egal.
Aber das hat ja ggf. nicht die gewünschte Funktion...

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


Lesenswert?

Dr. Sommer schrieb:
>> Gibt's bei C auch, aber ohne die Deklaration der Funktionen zu
>> sehen, kann man nicht sagen, was es genau ist.
> Und dem kann man nicht versehentlich auch eine Zahl außer 0 oder 1
> zuweisen?

Kann man, wird aber auf [false, true] abgebildet:
1
#include <stdbool.h>
2
3
bool getresult(int i)
4
{
5
  if (i < 0) return -1;
6
  if (i > 0) return true;
7
  return false;
8
}
Compiliert zwar ohne Warnung, gibt aber:
1
        .file   "foo.c"
2
__SP_H__ = 0x3e
3
__SP_L__ = 0x3d
4
__SREG__ = 0x3f
5
__tmp_reg__ = 0
6
__zero_reg__ = 1
7
        .text
8
.global getresult
9
        .type   getresult, @function
10
getresult:
11
/* prologue: function */
12
/* frame size = 0 */
13
/* stack size = 0 */
14
.L__stack_usage = 0
15
        ldi r18,lo8(1)
16
        or r24,r25
17
        brne .L2
18
        ldi r18,0
19
.L2:
20
        mov r24,r18
21
        ret
22
        .size   getresult, .-getresult
23
        .ident  "GCC: (GNU) 4.7.2"
Mal für den AVR compiliert, da ist es einfacher überschaubar, dass
wirklich nur 0 oder 1 als Ergebnis zurückgeliefert werden.  Für
amd64 compiliert ergibt sich:
1
        .file   "foo.c"
2
        .text
3
        .globl  getresult
4
        .type   getresult, @function
5
getresult:
6
.LFB0:
7
        .cfi_startproc
8
        testl   %edi, %edi
9
        setne   %al
10
        ret
11
        .cfi_endproc
12
.LFE0:
13
        .size   getresult, .-getresult
14
        .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4"
15
        .section        .note.GNU-stack,"",@progbits
Da hätte ich jetzt nachsehen müssen, was SETNE genau tut. ;-)  Aber
es ist auch hier eigentlich offensichtlich, dass es keine dreifache
Fallunterscheidung gibt, wie sie im Quellcode drin steht.

von Nils S. (kruemeltee) Benutzerseite


Lesenswert?

setne = set if not equal

von Klaus W. (mfgkw)


Lesenswert?

Jörg W. schrieb:
> Dr. Sommer schrieb:
>> Ja stimmt, ich war jetzt von C++ - "bool" Rückgabetypen ausgegangen, da
>> gibts nur "true" und "false".
>
> Gibt's bei C auch, aber ohne die Deklaration der Funktionen zu
> sehen, kann man nicht sagen, was es genau ist.
>
> Mit && ist es egal.

Man kann es auch in C passend schreiben:
1
    if( f1()!=0 & f2()!=0 & f3()!=0 & f4()!=0 ) ...
Damit hat man short circuit ausgehebelt, egal welchen genauen Wert die 
Funktionen liefern.
Falls man es braucht...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Quellkothistamin schrieb:
> bei solchen IFs rollt es mir immer die Zehennägel auf.

Warum? Short cut evaluation ist - wenn man es richtig anzuwenden weiß - 
einfach nur praktisch.

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


Lesenswert?

Frank M. schrieb:
> Short cut evaluation ist - wenn man es richtig anzuwenden weiß - einfach
> nur praktisch.

Ich erinnere mich noch an Pascal, bei dem man immer schreiben musste:
1
   if someptr <> nil then begin
2
      if @someptr = 42 then …
3
   end
weil es ebne keine definierte short cut evaluation gab, die garantiert
hätte, dass
1
   if (ptr != NULL && *ptr == 42) 
funktioniert.

von Tom (Gast)


Lesenswert?

Frank M. schrieb:
> short cut evaluation ist - wenn man es richtig anzuwenden weiß -
> einfach nur praktisch.

Wenn man Seiteneffekte in foo1(),...,foo4() hat (wenn es keine gibt, ist 
die Reihenfolge egal) und die short circuit evaluation implizit zur 
Ablaufsteuerung des Programms nutzt, verdient das sanfte Ohrfeigen wegen 
Cleverness.

Das Konstrukt müsste man dokumentieren. Oder, was einfacher wäre, den 
Code so schreiben, dass man sofort sieht, was unter welchen Bedingungen 
ausgeführt wird und dass das auch so gemeint ist.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Tom schrieb:
> Das Konstrukt müsste man dokumentieren. Oder, was einfacher wäre, den
> Code so schreiben, dass man sofort sieht, was unter welchen Bedingungen
> ausgeführt wird und dass das auch so gemeint ist.

Short circuit evaluation gehört zur Sprache C. Entweder versteht man sie 
oder man versteht sie nicht.

Trotzdem sollte man C-Code immer verständlich schreiben, klar. Aber wenn 
ich schreibe:
1
    if (foo1() &&
2
        foo2() &&
3
        foo3() &&
4
        foo4())
5
    {
6
         ...
7
    }
dann erwarte ich, dass der Leser dieses versteht als kürzere 
Schreibweise von:
1
    if (foo1())
2
    {
3
        if (foo2())
4
        {
5
            if (foo3())
6
            {
7
                if (foo4())
8
                {
9
                    ...
10
                }
11
            }
12
        }
13
    }
Wenn nicht, kann er kein C.

P.S.
Ich erweitere meine obige Aussage:

Short circuit evaluation ist nicht nur praktisch, sondern auch besser 
lesbar.

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Übrigens:
Das Konstrukt
1
    if (foo1() ||
2
        foo2() ||
3
        foo3() ||
4
        foo4())
5
    {
6
         ...
7
    }
ist durch Eleganz und Aussagekraft kaum zu überbieten. Hier werden die 
Aufrufe von fooX() abgebrochen, sobald einer der vorangegangenen Aufrufe 
bereits TRUE zurückgeliefert hat.

Man überlege sich dazu die alternative Schreibweise, bei welcher man 
Short Circuit Evaluation nicht ausnutzt. Das gibt nämlich eine ganz 
lange und umständlich wirkende if-else-Kette.

von Bernd K. (prof7bit)


Lesenswert?

Jörg W. schrieb:
> Ich erinnere mich noch an Pascal, bei dem man immer schreiben musste:
>    if someptr <> nil then begin
>       if @someptr = 42 then …
>    end
>
> weil es ebne keine definierte short cut evaluation gab, die garantiert
> hätte, dass

Doch, die gibts dort auch. Außerdem wolltest Du ^ schreiben statt @.
1
if (someptr <> nil) and (someptr^ = 42) then begin
2
3
end;
http://www.freepascal.org/docs-html/prog/progsu4.html#x11-100001.2.4

1.2.4 $B or $BOOLEVAL : Complete boolean evaluation

By default, the compiler uses shortcut boolean evaluation, i.e., the 
evaluation of a boolean expression is stopped once the result of the 
total expression is known with certainty. The {$B } switch can be used 
to change this behaviour: if its argument is ON, then the compiler will 
always evaluate all terms in the expression. If it is OFF (the default) 
then the compiler will only evaluate as many terms as are necessary to 
determine the result of the complete expression.

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


Lesenswert?

Bernd K. schrieb:

>> weil es ebne keine definierte short cut evaluation gab, die garantiert
>> hätte, dass
>
> Doch, die gibts dort auch.

Nicht garantiert.  Kann jeder Compiler halten wie'n Dachdecker.

Portabler Code kann sich daher (anders als in C) nicht drauf
verlassen.

> Außerdem wolltest Du ^ schreiben statt @.

Nein, wollte ich nicht, sonst hätte ich das geschrieben.  Ich weiß,
was ich will.

Beide Zeichen sind in Pascal äquivalent.

von Bernd K. (prof7bit)


Lesenswert?

Jörg W. schrieb:
> Nein, wollte ich nicht, sonst hätte ich das geschrieben.  Ich weiß,
> was ich will.
>
> Beide Zeichen sind in Pascal äquivalent.

Offenbar weißt Du nicht was Du willst, das @ ist der address-of operator 
(also das Äquivalent zu & in C) und das ^ ist die 
Pointer-Dereferenzierung, (das Äquivalent zu * in C). Also lieber mal 
den Ball etwas flacher halten wenn man keine Ahnung hat!

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> http://www.freepascal.org/docs-html/prog/progsu4.html#x11-100001.2.4
>
> 1.2.4 $B or $BOOLEVAL : Complete boolean evaluation

Das scheint aber nicht in jedem Dialekt von Pascal garantiert zu sein.

Fußnoten aus https://en.wikipedia.org/wiki/Short-circuit_evaluation :

4 ISO Pascal allows but does not require short-circuiting.
5 ISO-10206 Extended Pascal supports and_then and or_else.

Bei ersterem würde ich das als Programmierer wegen fehlender Sicherheit 
nicht nutzen wollen, bei letzterem wurden dafür extra neue 
Schlüsselwörter eingeführt...

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> Offenbar weißt Du nicht was Du willst, das @ ist der address-of operator
> (also das Äquivalent zu & in C) und das ^ ist die
> Pointer-Dereferenzierung, (das Äquivalent zu * in C). Also lieber mal
> den Ball etwas flacher halten wenn man keine Ahnung hat!

Auszug aus 
http://www2.informatik.uni-halle.de/lehre/pascal/sprache/pas6ptr.html

----------------------- schnipp -----------------------------------
Mitunter wird auch @ anstelle von ^ verwendet.
Achtung:
In den Borland Pascal Systemen wird - im Widerspruch zum Pascal Standard 
- @ als Adreßoperator verwendet (siehe weiter unten).
----------------------- schnapp -----------------------------------

Mit Ahnung hat das also nichts zu tun, sondern eher mit dem verwendeten 
Pascal-Dialekt.

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


Lesenswert?

Bernd K. schrieb:
> Offenbar weißt Du nicht was Du willst, das @ ist der address-of operator
> (also das Äquivalent zu & in C) und das ^ ist die
> Pointer-Dereferenzierung, (das Äquivalent zu * in C).

Zu den Zeiten, da ich Pascal gemacht habe, war das eine einfach nur
ein Ersatzzeichen des anderen.  Hier der entsprechende Abschnitt
aus dem zugehörigen Standard ISO 7185:
1
6.1.9 Lexical alternatives
2
3
The representation for lexical tokens and separators given in 6 .1 .1
4
to 6 .1 .8, except for the character sequences (* and *), shall
5
constitute a reference representation for these tokens and separators.
6
To facilitate the use of Pascal on processors that do not support the
7
reference representation, the following alternatives have been
8
defined. All processors that have the required characters in their
9
character set shall provide both the reference representations and the
10
alternative representations, and the corresponding tokens or
11
separators shall not be distinguished. Provision of the reference
12
representations, and of the alterative token @, shall be
13
implementation-defined.  The alternative representations for the
14
tokens shall be
15
16
       Reference token Alternative token
17
18
           ↑                   @
19
           [                   (.
20
           ]                   .)
Kocht Freepascal hier sein eigenes Süppchen, oder steht das im
aktuellen Standard anders drin?

Edit: scheint unter diese "implementation-defined"-Klausel zu fallen.

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Kocht Freepascal hier sein eigenes Süppchen, oder steht das im
> aktuellen Standard anders drin?

Bernd redet offenbar von Borland Pascal oder Freepascal, siehe:

   Beitrag "Re: GCC: In welcher Reihenfolge wird if ausgewertet"

Bei Pascal von "Standards" zu sprechen ist offenbar recht schwierig, 
wenn sich keiner dran hält. Da ist C wesentlich stringenter.

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Bei Pascal von "Standards" zu sprechen ist offenbar recht schwierig,
> wenn sich keiner dran hält.

Das Problem von Pascal war es eben immer, dass einige in der Praxis
wichtige Dinge von den Standards nicht abgedeckt sind.  Die Bildung
einer Adresse eines Objekts ist sowas, Typumwandlung auch.

Damit erfindet dann dort jeder seins.

von Quellkothistamin (Gast)


Lesenswert?

> Warum? Short cut evaluation ist - wenn man es richtig anzuwenden weiß -
> einfach nur praktisch.

Als ob ich was gegen ShortcutEval hätte/gesagt hätte.

Mir geht es um folgendes:
1
  if( boolescherAusdruckTrueOderFalse )
2
     machwaszu True; //Ausduck ergab True
3
  else
4
     machwaszu False; //Ausdruck ergab False
Das IF-Statement führt also dazu dass GENAU machwaszu UND IN JEDEM 
FALL NUR machwaszu ausgeführt wird.
(im TO Bsp. schlicht "return ...")

Warum soll ich also machwaszu 2x schreiben, gar in verschiedenen 
Verzweigungsäste des Programmflusses?

Der Programmfluss will ja dass machwaszu ohnehin ausgeführt wird.

Dann kommt dieser Fall noch so frech daher dass das Argument von 
machwaszu identisch mit der Auswertung der Diskriminanten der IF 
Anweisung ist.

Ich erfasse die Absicht als "es geht um eine (1) Aktion", also muss dies 
nicht auf mehre Zeilen in 3 Statements (das IF, der THEN Zweig, der ELSE 
Zweig) verschleiert werden.

(viel geschwurbel - hoffe mein Punkt kommt nun durch :)

von Quellkothistamin (Gast)


Lesenswert?

> Das Problem von Pascal war es eben immer, dass einige in der Praxis
> wichtige Dinge von den Standards nicht abgedeckt sind.

Darum hat der Pascal-Vater und seine Entourage nicht geruht und die 
Entwicklung weitergetrieben.
Mit Modula-2 und Oberon sind dann auch ordentliche Systeme komplett 
geschrieben worden.
(so nebenbei: die stark gepiesakte wordyness m. BEGIN...END ist 
bereits in M-2 drastisch eliminiert - äätsch)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wie ist das eigentlich mit C++, ist das da genauso?

IIRC gibt's da ne Schikane wenn && oder || überladen sind, dann greift 
nämlich keine Shortcut-Eval.

von (prx) A. K. (prx)


Lesenswert?

Quellkothistamin schrieb:
> (viel geschwurbel - hoffe mein Punkt kommt nun durch :)

Nur ist das u.U. die Antwort auf die falsche Frage. Wenn man 
Beispielcode aus wenigen Zeilen analysiert, dann sollte man bedenken, 
dass solcher Code viel unnötigen Kram weglässt. Wodurch der Eindruck 
entstehen kann, dass es eine völlig andere bessere Lösung gibt - die 
dann aber nur auf den Beispielcode zutrifft, nicht aber auf den Code, 
der eigentlich dahinter steht.

Hier war die eigentliche Frage die Bedingung im "if" statement. Nicht 
aber, ob man hier true/false-Zweige überhaupt benötigt. Das war mit der 
ersten Antwort erledigt. Der Rest des Threads wurde dann ein bizarres 
Kaffeesatzlesen darüber, was Kaj vielleicht sonst noch gemeint haben 
könnte aber nicht zu fragen wagte und wie man diesen vereinfachten 
Beispielcode optisch oder sonstwie optimieren könnte.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Quellkothistamin schrieb:
> die stark gepiesakte wordyness m. BEGIN...END ist bereits in M-2
> drastisch eliminiert

Hat mich nie gestört.  Ich konnte schon Zehnfinger-Blindschreiben,
bevor ich mit Programmieren angefangen habe.

Johann L. schrieb:
> IIRC gibt's da ne Schikane wenn && oder || überladen sind, dann greift
> nämlich keine Shortcut-Eval.

Möglich, da kenne ich C++ zu wenig.  Müsste ich auch im Standard
nachlesen.

Mir reicht es im Moment, wenigstens im C-Standard so einigermaßen
schnell genug das zu finden, was ich gerade suche. :)

von Falk B. (falk)


Lesenswert?

@Johann L. (gjlayde) Benutzerseite

>Wie ist das eigentlich mit C++, ist das da genauso?

Keine Ahnung!

Aber trotzdem sollte man von solchen Short Circuit Evaluations eher 
vorsichtig Gebrauch machen. Bei reinen logischen Ausdrücken ist das OK 
und birgt keine Gefahr, bei Funktionsaufrufen mit Nebeneffekten (Zugriff 
auf IO-Register, globale Variablen, etc.) kann sowas ins Auge gehen. 
Nicht nur beim originalen Verfasser, auch bei Nachtuzern, 
"Pflegepersonal" und last but not least beim Debuggen! Dort sollte man 
lieber ein paar Zeilen mehr tippen und die Situation EINDEUTIG im 
Quelltext darstellen. Lesbarkeit geht DEUTLICH vor Tippeffizienz!

von Sebastian S. (amateur)


Lesenswert?

Gab's da nicht einen Compilerschalter bzw. Kommandozeilenparameter, mit 
dem man die "Abkürzungen" abschalten kann?

von Klaus W. (mfgkw)


Lesenswert?

Jörg W. schrieb:
> Johann L. schrieb:
>> IIRC gibt's da ne Schikane wenn && oder || überladen sind, dann greift
>> nämlich keine Shortcut-Eval.
>
> Möglich, da kenne ich C++ zu wenig.  Müsste ich auch im Standard
> nachlesen.

Oder ausprobieren...
Ist so, bei selbstgebautem && kennt mein gcc auch kein short circuit.

von (prx) A. K. (prx)


Lesenswert?

Sebastian S. schrieb:
> Gab's da nicht einen Compilerschalter bzw. Kommandozeilenparameter, mit
> dem man die "Abkürzungen" abschalten kann?

Hoffentlich nicht. In C wäre das ungefähr so sinnvoll wie ein Schalter 
zum Vertauschen der Bedeutung von + und -.

von B. S. (bestucki)


Lesenswert?

Johann L. schrieb:
> Wie ist das eigentlich mit C++, ist das da genauso?
>
> IIRC gibt's da ne Schikane wenn && oder || überladen sind, dann greift
> nämlich keine Shortcut-Eval.

Siehe hier:
http://stackoverflow.com/questions/16031812/lazy-overloaded-c-operator

von (prx) A. K. (prx)


Lesenswert?

C++ bietet eben gegenüber C sehr viel elegantere und unauffälligere 
Wege, sich ein Grab zu schaufeln.

von Klaus W. (mfgkw)


Lesenswert?

Klar: C++ enthält C ziemlich komplett, mit allen Fehlermöglichkeiten.
Und bringt viele neue Möglichkeiten dazu.

Da können die Fehlermöglichkeiten nicht weniger werden.

(PS: Trotzdem kann man in C++ wesentlich fehlerärmer programmieren.
Aber halt nicht, wenn man jeden Mist macht, sondern mit einem passenden 
Stil.)

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

Johann L. schrieb:
> Wie ist das eigentlich mit C++, ist das da genauso?

Ja für Builtin Types gibts genauso Short Circuit Evaluation. Erst wenn 
man && überläd gibts das nicht mehr. Zum Glück kann man aber operator&& 
nur überladen wenn wenigstens eine Klasse oder so dabei ist. Für bool && 
bool kann man also nichts kaputt machen.

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.
Lade...