Forum: PC-Programmierung Copy Constructor Riddle


von Simon H. (simi)


Lesenswert?

Hallo zusammen,

kann sich das jemand erklären? Ich habe keine Ahnung, was da passiert! 
(um kein Spielverderber zu sein, schreibe ich die Lösung jetzt mal nicht 
hin.)

Die Masterfrage ist: Was tut dieser Code?

http://cryp.to/default-constructor-riddle/
1
#include <string>
2
#include <iostream>
3
4
int main(int, char * [])
5
{
6
  std::string prefix("->"), middle(), suffix("<-");
7
  std::cout << "This space has been intentionally left blank: "
8
            << prefix << middle << suffix
9
            << std::endl;
10
  return 0;
11
}

Gruäss
Simon

Ups, gerade gesehen, dass der Threadtitel falsch ist...

: Verschoben durch User
von Simon H. (simi)


Lesenswert?

Hab's gerade kapiert! :-)

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


Lesenswert?

Simon Huwyler schrieb:
> Hab's gerade kapiert! :-)

Verrätst du's uns?

Da die Warnung in Zeile 8 kommt, vermute ich mal, dass es irgendwas
damit zu tun hat, wie der <<-Operator agiert.

von Simon H. (simi)


Lesenswert?

Verdammt, ich war zu voreilig! Ok, middle ist kein String, sondern eine 
Funktion, die keine Parameter nimmt, und einen String zurückgibt... Und 
die gibt offenbar 'true' zurück, das dann in "1" verwandelt wird... aber 
warum true? Ist das einfach by default so, wenn nichts implementiert 
ist?

von welche ausgabe (Gast)


Lesenswert?

Hier wird doch eine Funktion

std::string middle()

deklariert, die nicht definiert ist.
Der Linker sollte also einen entsprechenden Fehler ausgeben.
Oder bin ich auf dem Holzweg?

von Simon H. (simi)


Lesenswert?

Sollte, ja! grrrrrrr..... ich versteh's nicht!


... nein, der Linker motzt ja nur, wenn wir die Funktion auch wirklich 
aufrufen. Tun wir das? Nein, eigentlich nicht, weil ein Funktionsaufruf 
Klammern haben muss... aber was tun wir denn?

Hmmmm... Die Adresse dieser nicht definierten Funktion scheint 1 zu 
sein.... häääää?

von welche ausgabe (Gast)


Lesenswert?

Ich habe es gerade mal unter VC++ 10 ausprobiert;
Der Linker gibt die erwartete Fehlermeldung aus.

von Peter II (Gast)


Lesenswert?

warning: the address of 'std::string middle()' will always evaluate as 
'true' [-Waddress]

von Simon H. (simi)


Lesenswert?

GCC compiliert es problemlos, und die Ausgabe ist:

This space has been intentionally left blank: ->1<-

von Simon H. (simi)


Lesenswert?

Peter II schrieb:
> warning: the address of 'std::string middle()' will always evaluate as
> 'true' [-Waddress]

Genau, das warnt er. Aber warum ist die Adresse dieser nicht definierten 
Funktion 1 (was halt auch 'true' entspricht)?

von mabla (Gast)


Lesenswert?

Du musst die Option -Wall setzen.

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


Lesenswert?

Simon Huwyler schrieb:
> Verdammt, ich war zu voreilig! Ok, middle ist kein String, sondern eine
> Funktion, die keine Parameter nimmt, und einen String zurückgibt... Und
> die gibt offenbar 'true' zurück, das dann in "1" verwandelt wird... aber
> warum true? Ist das einfach by default so, wenn nichts implementiert
> ist?

Verstehe ich auch nicht ganz.  Es wird nur der Name der Funktion
genannt, sie wird also nicht aufgerufen.  Das ist ja eigentlich
gleichbedeutend mit dem Ermitteln der Adresse der Funktion (und
dahin deutet auch die Warnung).

Dass es eine Funktion ist, erkennt man, wenn man das noch
nachsetzt:
1
std::string x = middle;

Wenn middle ein String wäre, würde das gehen.  Die Fehlermeldung ist.
1
foo.C:10: error: conversion from ‘std::string ()()’ to non-scalar type ‘std::basic_string<char, std::char_traits<char>, std::allocator<char> >’ requested

(Man sieht die Funktionsklammern.)

von Peter II (Gast)


Lesenswert?

Simon Huwyler schrieb:
> Genau, das warnt er. Aber warum ist die Adresse dieser nicht definierten
> Funktion 1 (was halt auch 'true' entspricht)?

weil jede funktion irgendwo an einer Adresse != 0 liegt. Und != 0 ist 
true. Und True wird 1 beim Umwandeln nach int.

von Marwin (Gast)


Lesenswert?

Simon Huwyler schrieb:
> Genau, das warnt er. Aber warum ist die Adresse dieser nicht definierten
> Funktion 1 (was halt auch 'true' entspricht)?

Sie ist nicht 1. Sie ist != 0 und wird in boolean gewandelt, was dann 
den Wert 1 ergibt.

  std::cout << "This space has been intentionally left blank: "
            << prefix << "" << (void (*)()) 0x23435 << suffix
            << std::endl;

Geht auch.

Das ganze ist nur ein weiteres Beispiel dafuer, warum viele der Features 
in C++ mehr schaden als nutzen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Simon Huwyler schrieb:
> Genau, das warnt er. Aber warum ist die Adresse dieser
> nicht definierten Funktion 1 (was halt auch 'true' entspricht)?
Wenn du das cout wegnimmst ist auch die Warnung weg. Der Compiler wählt 
den ersten passenden Streamoperator (in diesem Fall wohl bool) auf den 
er die Adresse casten kann.
Da die Adresse einer Funktion nie 0 sein kann ist der Ausdruck immer 
wahr und kann durch eine konstante ersetz werden (true), also ist es 
auch egal das es die Funktion gar nicht gibt.
Der Rest ist nur Verwirrungstaktik und zeigt, dass zuviel gesetzte 
Klammern manchmal merkwürdige Auswirkungen haben können (ohne Klammern 
hinter middle gibt es das 'erwartete' Ergebnis).

von Karl H. (kbuchegg)


Lesenswert?

Marwin schrieb:

> Das ganze ist nur ein weiteres Beispiel dafuer, warum viele der Features
> in C++ mehr schaden als nutzen.

Das hat aber weniger mit C++ an sich zu tun, sondern damit wie sie im 
gcc umgesetzt sind.
Ein "ordentlicher" Compiler hätte die Verwendung einer Funktion middle() 
im Objektfile hinterlassen und der Linker hätte gemeckert, dass es diese 
Funktion nicht gibt.

Nichts davon hat mit C++ an sich zu tun, sondern mit einer konkreten 
Implementierung.

von welche ausgabe (Gast)


Lesenswert?

Auch wenn es sicher berechtigte Kritik an der Sprache C++ gibt, hier 
würde ich das nicht so sehen.
Die Schwachstelle liegt hier doch eher in der Implementierung eines 
Compilers bzw. der stream-Bibliothek.

von Marwin (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Das hat aber weniger mit C++ an sich zu tun, sondern damit wie sie im gcc 
umgesetzt sind.

Doch sicher. Bei vernuenftigem Sprachdesign wuerde in diesem Fall 
zumindest ueber die Zweideutig in Zusammenhang mit dem 
std::string-Konstruktor gewarnt. Dazu kommt noch die Dusseligkeit, dass 
"middle" einfach die Adresse der Funktion liefert - wozu gibt es den 
Adressoperator?

von Simon H. (simi)


Lesenswert?

Also, ich habe jetzt mal folgendes gemacht: middle() habe ich durch 
foo() ersetzt, welches ich zuvor real implementiert habe. Mit 
Rückgabewert std::string.

Das gibt ebenfalls ein 1.

Und wenn ich cout << &foo << ...

probiere, kommt auch 1 zurück! Also ist das tatsächlich cout, das aus 
einer Adresse ein 'true', resp. eine '1' macht??? Nach meiner Logik 
hätte ich da irgend eine Adresse im Textsegment kriegen sollen!?!

von Karl H. (kbuchegg)


Lesenswert?

> Der Rest ist nur Verwirrungstaktik und zeigt, dass zuviel
> gesetzte Klammern manchmal merkwürdige Auswirkungen haben können

Das wiederrum ist Absicht.

Das es diese Mehrdeutigkeit in C++ gibt, war auch den Machern bewusst.
Daher gibt es eine Regel:

Wenn etwas wie ein Funktionsprototyp aussieht, ist es auch einer.

  myclass foo();

ist IMMER ein Funktionsprototyp, wohingegen es für

  myclass foo( 5 );

keine Möglichkeit gibt, jemals einer zu werden. Das ist daher immer eine 
Variablendefinition.

von welche ausgabe (Gast)


Lesenswert?

Du solltest aber beachten, dass auch std::string nichts mit dem 
Sprachdesign zu tun hat, sondern Bestandteil einer Bibliothek ist.

von Karl H. (kbuchegg)


Lesenswert?

Marwin schrieb:
> Karl Heinz Buchegger schrieb:
>> Das hat aber weniger mit C++ an sich zu tun, sondern damit wie sie im gcc
> umgesetzt sind.
>
> Doch sicher.

Ne, sorry.
Das ist eindeutig eine Sache der Implementierung.
Die Adresse einer Funktion wird benutzt. Wenn der Optimizer da dann 
damit rumwurschtelt und die Funktion komplett aus dem Spiel bringt, muss 
die Funktion trotzdem noch existieren und der Compiler hat dafür Sorge 
zu tragen, dass ALLE daraus folgenden Konsequenzen auch eingehalten 
werden.

Hier wurde eindeutig die 'As if' Regel verletzt.

von Marwin (Gast)


Lesenswert?

welche ausgabe schrieb:
> Du solltest aber beachten, dass auch std::string nichts mit dem
> Sprachdesign zu tun hat, sondern Bestandteil einer Bibliothek ist.

Die zur Sprache gehoert. Leider, ist aber so.

von Marwin (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Ne, sorry.
> Das ist eindeutig eine Sache der Implementierung.

Dass du als C++-Fan das nicht gelten laesst, ist mir voellig klar. Aber 
Jemandem, der sich die Sache von Aussen ansieht, ist ueberhaupt nicht zu 
vermitteln, wieso er den leeren Konstruktor nicht genauso verwenden 
darf, wie den nicht leeren. Und wie viele Leute hier dadurch voellig 
verwirrt werden, ist ein weiterer Beleg dafuer. Sowas als "richtig" zu 
betrachten zeugt, mit Verlaub, von einer sehr elitaeren Haltung. Eine 
Sprache, die sehr maechtig ist, erlaubt auch fiese Fehler - aber sie 
sollte nicht unnoetig zu Fehlern verleiten.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Simon Huwyler schrieb:
> probiere, kommt auch 1 zurück! Also ist das tatsächlich cout, das aus
> einer Adresse ein 'true', resp. eine '1' macht??? Nach meiner Logik
> hätte ich da irgend eine Adresse im Textsegment kriegen sollen!?!

Es gibt aber keinen expliziten Streamoperator dafür! Den musst du 
definieren oder vorher explizit in einen anderen Typen wandeln.

von Simon H. (simi)


Lesenswert?

Marwin schrieb:
> Sie ist nicht 1. Sie ist != 0 und wird in boolean gewandelt, was dann
> den Wert 1 ergibt.
>
>   std::cout << "This space has been intentionally left blank: "
>             << prefix << "" << (void (*)()) 0x23435 << suffix
>             << std::endl;
>
> Geht auch.

Hab's mal in die umgekehrte Richtung probiert: cout durch printf 
ersetzt. Dann kommt eine Adresse raus. :-)

von Rolf Magnus (Gast)


Lesenswert?

Marwin schrieb:
> Karl Heinz Buchegger schrieb:
>> Das hat aber weniger mit C++ an sich zu tun, sondern damit wie sie im gcc
> umgesetzt sind.
>
> Doch sicher. Bei vernuenftigem Sprachdesign wuerde in diesem Fall
> zumindest ueber die Zweideutig in Zusammenhang mit dem
> std::string-Konstruktor gewarnt.

Da ist überhaupt nichts zweideutig. Die Sprache definiert die Bedeutung 
völlig eindeutig. Warum sollte sie vom Compiler eine Warnung fordern?
Der Compilerhersteller kann sich natürlich dazu entschließen, eine 
Warnung auszugeben, wenn er der Meinung ist, daß der Code 
mißverständlich ist. Nur: Was, wenn der Programmierer das wirklich so 
meinte, wie er es hingeschrieben hat? Soll der dann mit einer in seinem 
Fall unsinnigen Warnung leben müssen?

> Dazu kommt noch die Dusseligkeit, dass "middle" einfach die Adresse der Funktion
> liefert - wozu gibt es den Adressoperator?

Das ist eher ein Erbe von C, wo das schon immer so war.

von Karl H. (kbuchegg)


Lesenswert?

Marwin schrieb:
> Karl Heinz Buchegger schrieb:
>> Ne, sorry.
>> Das ist eindeutig eine Sache der Implementierung.
>
> Dass du als C++-Fan das nicht gelten laesst, ist mir voellig klar.

Das hat mit C++ Fan nichts zu tun.
Die Sprache hat eine Definition. Und wenn wir uns an die halten, stellt 
sich raus: Das ist ein Fehler in der Implementierung.
Wenn du den Sprachstandard nicht gut genug kennst, kann ich doch nichts 
dafür.

von welche ausgabe (Gast)


Lesenswert?

Es würde zu weit und auch zu keinem Ergebnis führen, über die Probleme 
zu diskutieren, die sich aus der Abhängigkeit von C ergeben haben.

Meine persönliche Meinung ist:
Es gibt Sprachen, die lässiger verwendbar sind und damit evtl. schneller 
zu Resultaten führen, dafür aber andere Nachteile besitzen.
Wer sich bewußt für die Verwendung von C/C++ entscheidet, sollte bereit 
sein, sich intensiv mit seinen Werkzeugen vertraut zu machen.

Oder: There is no free meal

von Peter II (Gast)


Lesenswert?

Läubi .. schrieb:
> Da die Adresse einer Funktion nie 0 sein kann ist der Ausdruck immer
> wahr und kann durch eine konstante ersetz werden (true)

die Frage ist ob das wirklich so ist? Warum sollte ich bei z.b. bei 
einem AVR keine funktion an die adresse 0 legen können?

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Peter II schrieb:
> die Frage ist ob das wirklich so ist? Warum sollte ich bei z.b. bei
> einem AVR keine funktion an die adresse 0 legen können?

Weil per Definition ein Pointer auf 0 nicht initialisiert ist. Die '0' 
ist also schon vergeben. Theoretisch kann dort natürlich eine Funktion 
liegen keine Frage.

von Karl H. (kbuchegg)


Lesenswert?

Damit da keine Missverständnisse aufkommen.

Bei diesem Programm
1
#include <string>
2
#include <iostream>
3
4
int main(int, char * [])
5
{
6
  std::string prefix("->"), middle(), suffix("<-");
7
  std::cout << "This space has been intentionally left blank: "
8
            << prefix << middle << suffix
9
            << std::endl;
10
  return 0;
11
}

lautet die eigentliche Frage nicht "Warum kommt da der Output raus, der 
rauskommt?"

Dann davor steht immer noch die Frage: Ist das überhaupt ein gültiges 
C++ Programm?
Und die Antwort darauf lautet: Nein, ist es nicht.

Compiler/Linker hätten hier überhaupt kein ausführbares Programm 
erzeugen dürfen.

von Marwin (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Das hat mit C++ Fan nichts zu tun.

Doch, dafuer bist du bekannt. Jede Kritik an C++ lehnst du mit dem 
Verweis auf den Standard ab. So wie jetzt wieder:

> Die Sprache hat eine Definition. Und wenn wir uns an die halten, stellt
> sich raus: Das ist ein Fehler in der Implementierung.

Ja, die Sprache hat eine Definition. Und die ist das Problem. Nur weil's 
standardisiert ist, wird's nicht automatisch gut und sinnvoll.

> Wenn du den Sprachstandard nicht gut genug kennst, kann ich doch nichts
> dafür.

Was soll das? Keine Argumente mehr?

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> Compiler/Linker hätten hier überhaupt kein ausführbares Programm
> erzeugen dürfen.

Sicher? Letztendlich wirft der Compiler die Funktion raus, weil "brauch 
ich nicht", und der Linker kriegt die dann garnicht zu Gesicht.. aber so 
Fit bin ich bei C++ auch nicht.

von Peter II (Gast)


Lesenswert?

Läubi .. schrieb:
> Weil per Definition ein Pointer auf 0 nicht initialisiert ist. Die '0'
> ist also schon vergeben.

nein, nicht per Definition. Sonst könnte man man nie den Ram ab adresse 
0 verwenden.

von welche ausgabe (Gast)


Lesenswert?

Ganz so einfach ist es nicht, es wird ja auf den Bezeichner "middle" 
zugegriffen.

Es bleibt ein Implementierungsproblem.

von Karl H. (kbuchegg)


Lesenswert?

Marwin schrieb:

> Ja, die Sprache hat eine Definition. Und die ist das Problem. Nur weil's
> standardisiert ist, wird's nicht automatisch gut und sinnvoll.

Das hab ich auch nicht gesagt.
Ich habe genügend andere Kritikpunkte am C++ Standard. Nur keine Sorge.

>> Wenn du den Sprachstandard nicht gut genug kennst, kann ich doch nichts
>> dafür.
>
> Was soll das? Keine Argumente mehr?

Das Argument lautet:
Wer den Standard kritisiert sollte den Standard auch soweit kennen, dass 
er unterscheiden kann zwischen
* das ist per Design so
* das ist deshalb so, weil es der Compilerbauer verbockt hat.

Dieser konkrete Fall fällt in letztere Kategorie. Auch wenn du 
hartnäckig versuchst, es in erstere zu verlagern.

von (prx) A. K. (prx)


Lesenswert?

Karl Heinz Buchegger schrieb:

> Das hat aber weniger mit C++ an sich zu tun,

Doch. Die Syntax von C++ ist grauenhaft. Ein Kernproblem hierbei ist die 
in C ursprünglich beabsichtigte optische Analogie von 
Funktionsdefinition und Funktionsaufruf. Das rächt sich hier, man kann 
Aufruf und Deklaration kaum unterscheiden.

von Rolf Magnus (Gast)


Lesenswert?

Peter II schrieb:
> Läubi .. schrieb:
>> Da die Adresse einer Funktion nie 0 sein kann ist der Ausdruck immer
>> wahr und kann durch eine konstante ersetz werden (true)
>
> die Frage ist ob das wirklich so ist? Warum sollte ich bei z.b. bei
> einem AVR keine funktion an die adresse 0 legen können?

Beim AVR würde ich jetzt an Adresse 0 keine Funktion legen, da dort der 
Restvektor der Interruptvektortabelle liegt. Aber unabhängig davon ist 
in C++ nicht vorgeschrieben, daß bei einem Nullzeiger alle Bits 0 zu 
sein haben. Was aber vorgeschrieben ist, ist, daß eine gültige Adresse 
einer Funktion (oder eines Objekts) niemals einem Nullzeigerwert 
entsprechen darf.

von Karl H. (kbuchegg)


Lesenswert?

Läubi .. schrieb:
> Karl Heinz Buchegger schrieb:
>> Compiler/Linker hätten hier überhaupt kein ausführbares Programm
>> erzeugen dürfen.
>
> Sicher? Letztendlich wirft der Compiler die Funktion raus, weil "brauch
> ich nicht",


Oberster Grundsatz beim Optimieren (sowas wie die Grunddirektive) ist:

Der Compiler darf alles optimieren, was er will. Aber: Der Endeffekt 
muss sein, dass sich die sichtbaren Auswirkungen so verhalten, als ob 
(as if) die Optimierung nie stattgefunden hätte.

Es gibt nur eine einzige Optimierung, die im C++ Standard erwähnt wird 
und dies auch nur deshalb, weil sie Auswirkungen hat, die das 
beobachtete Verhalten verändern. Das ist die 'Named return value 
optimization'. Für diese Optimierung wurde zugelassen, dass sich das 
Verhalten ändert. Aber alle anderen Optimierungen haben dem Grundsatz zu 
gehorchen: 'As if'

Der Compiler muss das so übersetzen

     nimm die Adresse der Funktion
     konvertiere sie in einen bool


Das ist das was der Programmierer dort hingeschrieben hat. Wenn der 
Optimizer seine Kentnisse ausnutzt, dass auf diesem System eine Funktion 
niemals an der Speicheradresse 0 liegen kann, dann ist das zwar nett, 
entbindet aber das Programm nicht von der Verpflichtung, dass die 
Funktion in erster Linie existieren muss, damit man ihre Adresse 
ermitteln kann. Denn wenn der Optimizer nicht optimiert hätte, würde ja 
genau das passieren.

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


Lesenswert?

Karl Heinz Buchegger schrieb:
> Dann davor steht immer noch die Frage: Ist das überhaupt ein gültiges
> C++ Programm?
> Und die Antwort darauf lautet: Nein, ist es nicht.

Naja, dann nimm das hier:
1
#include <string>
2
#include <iostream>
3
4
int main(int, char * [])
5
{
6
  std::string prefix("->"), middle(), suffix("<-");
7
  std::cout << "This space has been intentionally left blank: "
8
            << prefix << middle << suffix
9
            << std::endl;
10
  return 0;
11
}
12
13
std::string middle(void) { return "0"; }

Dürfte auch in deinen Augen ein gültiges C++-Programm sein, hat
ansonsten natürlich die gleichen verwundersamen Effekte, nur dass
man die Auflösung des Knotens bereits ein wenig besser suggeriert
bekommt.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Karl Heinz Buchegger schrieb:
>
>> Das hat aber weniger mit C++ an sich zu tun,
>
> Doch. Die Syntax von C++ ist grauenhaft.

Geb ich dir durchaus recht.
Nichts desto trotz hat der Fall hier nichts mit dem C++ Standard zu tun.


*********************************************************************

Leute: Bitte unterscheidet zwischen dem, was sich aus der 
Sprachdefinition ergibt und dem was ein konkretes System daraus macht!

Wenn es um die Analyse von Problemen geht, ist das ein wichtiger Punkt!

*********************************************************************

von welche ausgabe (Gast)


Lesenswert?

Ist die Funktion middle() definiert, sollte ein vernünftige 
Implementierung die Adresse der Funktion ausgeben.

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch schrieb:

> Naja, dann nimm das hier:
>
>
1
> #include <string>
2
> #include <iostream>
3
> 
4
> int main(int, char * [])
5
> {
6
>   std::string prefix("->"), middle(), suffix("<-");
7
>   std::cout << "This space has been intentionally left blank: "
8
>             << prefix << middle << suffix
9
>             << std::endl;
10
>   return 0;
11
> }
12
> 
13
> std::string middle(void) { return "0"; }
14
>
>
> Dürfte auch in deinen Augen ein gültiges C++-Programm sein

Jetzt erfüllt es schon mal den ersten Punkt: Es ist gültig

> hat
> ansonsten natürlich die gleichen verwundersamen Effekte

Zweifellos. Und bei diesem Programm schlage ich mich sofort auf die 
andere Seite und stimme in den Chor der Kritiker ein, warum eigentlich 
Funktionsprototypen an dieser Stelle überhaupt zugelassen sind, was auch 
in meinen Augen schwachsinnig ist und ein Überbleibsel aus der K&R Zeit 
darstellt.

von Rolf Magnus (Gast)


Lesenswert?

welche ausgabe schrieb:
> Ist die Funktion middle() definiert, sollte ein vernünftige
> Implementierung die Adresse der Funktion ausgeben.

Nein. Sie sollte dennoch eine 1 ausgeben, da es keinen Stream-Operator 
für Funktionszeiger gibt und die Adresse deshalb nach bool konvertiert 
wird.

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


Lesenswert?

welche ausgabe schrieb:
> Ist die Funktion middle() definiert, sollte ein vernünftige
> Implementierung die Adresse der Funktion ausgeben.

Nein.

von Karl H. (kbuchegg)


Lesenswert?

Rolf Magnus schrieb:
> welche ausgabe schrieb:
>> Ist die Funktion middle() definiert, sollte ein vernünftige
>> Implementierung die Adresse der Funktion ausgeben.
>
> Nein. Sie sollte dennoch eine 1 ausgeben, da es keinen Stream-Operator
> für Funktionszeiger gibt und die Adresse deshalb nach bool konvertiert
> wird.

Auch so ein Punkt, den man durchaus kritisieren kann.

von welche ausgabe (Gast)


Lesenswert?

Ich habe bewußt "vernünftig" im Sinne von "sinnvoll" geschrieben.

Ich bin nur ein einfacher Praktiker, theoretisieren liegt mir nicht.
Das überlasse ich den dafür qualifizerten Menschen.

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


Lesenswert?

welche ausgabe schrieb:
> Ich habe bewußt "vernünftig" im Sinne von "sinnvoll" geschrieben.

Du hast aber impliziert, dass es einer Implementierung frei stehen
würde, dies zu tun.  Dem ist nicht so.  Wenn die Sprache das nicht
zulässt, dann ist alles, was die Implementierung tun kann, die
Ausgabe einer Warnung.  Das wiederum passiert ja hier auch.

Dass man die Sprache an dieser Stelle hätte vernünftiger definieren
können, steht auf einem anderen Blatt.  Da sind wir uns wohl alle
einig.  Ich kann mir keinen praktischen Nährwert vorstellen, warum
die Definition eben genau so ist, dass ein Funktionszeiger zu einem
Bool mutiert.  (Ein Objektzeiger tut dies übrigens nicht, sondern
wird sehr wohl als Adresse ausgegeben.  Wenn man also "(void*)middle"
schreibt und middle auch wirklich da ist, dann gibt's die Adresse
in der Ausgabe.  Allerdings dürfte dieser Typecast undefiniertes
Verhalten sein, weil der Standard einer Maschine nicht vorschreiben
will, dass jeglicher Funktionszeiger eindeutig in einen Objektzeiger
abbildbar ist.  Zumindest ist es unter C so.)

von welche ausgabe (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Du hast aber impliziert, dass es einer Implementierung frei stehen
> würde, dies zu tun.  Dem ist nicht so.  Wenn die Sprache das nicht
> zulässt, dann ist alles, was die Implementierung tun kann, die
> Ausgabe einer Warnung.  Das wiederum passiert ja hier auch.

Du hast natürlich recht, meine Aussage spiegelte die eindeutig 
eingeschränkte Sichtweise eines auf nur einer Platform arbeitenden 
Praktikers wieder. Bei grundsätzlicher Betrachtung des Problems greift 
meine Sichtweise zu kurz.

von Rolf M. (rmagnus)


Lesenswert?

Jörg Wunsch schrieb:
> Ich kann mir keinen praktischen Nährwert vorstellen, warum die Definition eben
> genau so ist, dass ein Funktionszeiger zu einem Bool mutiert.

Er hat keine andere Wahl, da es zu nichts anderem, für das 
Stream-Operatoren exisiteren, eine implizite Konvertierung gibt.

>  (Ein Objektzeiger tut dies übrigens nicht, sondern wird sehr wohl als Adresse
> ausgegeben.

Da greift die Konvertierung nach void*.

>  Wenn man also "(void*)middle" schreibt und middle auch wirklich da ist, dann
> gibt's die Adresse in der Ausgabe.

Ist aber eigentlich auch nicht konform.

> Allerdings dürfte dieser Typecast undefiniertes Verhalten sein, weil der 
Standard
> einer Maschine nicht vorschreiben will, dass jeglicher Funktionszeiger eindeutig 
in
> einen Objektzeiger abbildbar ist.

Das geht eigentlich viel weiter: Er verbietet es den Implementationen, 
eine Konvertierung (egal ob implizit oder explizit) überhaupt 
anzubieten. Und ja, das bedeutet, daß die meisten Compiler hier nicht 
konform sind.

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


Lesenswert?

Rolf Magnus schrieb:
>> Ich kann mir keinen praktischen Nährwert vorstellen, warum die Definition eben
>> genau so ist, dass ein Funktionszeiger zu einem Bool mutiert.
>
> Er hat keine andere Wahl, da es zu nichts anderem, für das
> Stream-Operatoren exisiteren, eine implizite Konvertierung gibt.

Ich meinte damit, dass ich mir vorstellen kann, dass man dies im
Standard auch anders lösen könnte (indem man eben einen operator <<
auch für Funktionszeiger anbietet), ohne dass man bestehende
Programme damit gefährdet, denn das jetzige Verhalten (implizite
Umwandlung nach Bool) hat keine vorstellbare praktische Relevanz.

von Rolf M. (rmagnus)


Lesenswert?

Und wie soll dieser Operator dann konkret aussehen? Es gibt keinen 
generischen Funktionszeigertyp, in den es implizit konvertierbar wäre. 
Das einzige wäre ein void (*)(void), der aber (aus verständlichen 
Gründen) eine explizite Konvertierung erfordert.

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


Lesenswert?

Rolf Magnus schrieb:
> Und wie soll dieser Operator dann konkret aussehen? Es gibt keinen
> generischen Funktionszeigertyp, in den es implizit konvertierbar wäre.
> Das einzige wäre ein void (*)(void), der aber (aus verständlichen
> Gründen) eine explizite Konvertierung erfordert.

Warum?  Was ist am Typ void (*)(void) als generischem Funktionszeiger-
typ denn anders als an void * als generischen Objektzeigertyp?  Ist
doch nur eine Definitionsfrage.  OK, ein Objekt vom Typ void * kann
man nie dereferenzieren, einen Funktionszeiger vom Typ void (*)(void)
schon, aber das ändert nichts daran, dass man selbigen durchaus per
definitionem zu einem generischen Funktionszeiger machen kann, wenn
man will.  Schließlich hatte C anfangs auch noch keine void *, und
man hat char * als generischen Objektzeigertypen missbraucht.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Bei diesem Programm#include <string>
> #include <iostream>
>
> int main(int, char * [])
> {
>   std::string prefix("->"), middle(), suffix("<-");
>   std::cout << "This space has been intentionally left blank: "
>             << prefix << middle << suffix
>             << std::endl;
>   return 0;
> }
>
> lautet die eigentliche Frage nicht "Warum kommt da der Output raus, der
> rauskommt?"
>
> Dann davor steht immer noch die Frage: Ist das überhaupt ein gültiges
> C++ Programm?
> Und die Antwort darauf lautet: Nein, ist es nicht.

So sehe ich das auch, auch wenn ich mir nicht hundertprozentig sicher
bin. Die Frage ist, ob in diesem Fall die Funktion middle definiert
werden muss. Im Draft von ISO 14882:2011 steht:

  "An expression is potentially evaluated unless it is an unevaluated
  operand (Clause 5) or a subexpression thereof. A variable or
  non-overloaded function whose name appears as a potentially-evaluated
  expression is odr-used unless it is an object that satisfies the
  requirements for appearing in a constant expression (5.19) and the
  lvalue-to-rvalue conversion (4.1) is immediately applied.
  […]
  Every program shall contain exactly one definition of every non-inline
  function or variable that is odr-used in that program; […]"

middle ist nach dieser Definition auf jeden Fall "potentially evaluated"
und vermutlich auch "odr-used", wobei ich die Definition von odr-used
nicht bis ins letzte Detail verstanden habe.

> Compiler/Linker hätten hier überhaupt kein ausführbares Programm
> erzeugen dürfen.

Doch, das wohl schon:

  "Every program shall contain exactly one definition of every non-
  inline function or variable that is odr-used in that program; no
  diagnostic required."

"No diagnostic required" heißt, dass der Compiler/Linker keine Fehler-
meldung ausgeben muss.

Fazit: Das Programm verstößt zwar vermutlich gegen den Standard, nicht
aber der Compiler/Linker, der es trotzdem akzeptiert.


Ein ähnlich gelagerter und etwas leichter zu durchschauender Fall ist
übrigens folgender:
1
#include <iostream>
2
3
extern unsigned int u;
4
5
int main() {
6
7
  if (u<0)
8
    std::cout << "negativ" << std::endl;
9
  else
10
    std::cout << "nichtnegativ" << std::endl;
11
12
  return 0;
13
}

Hier ist die gewöhnliche Variable u nicht definiert und odr-used, das
Programm ist also nicht standardkonform. Trotzdem wird es von den
GNU-Tools zulässigerweise gebaut (es gibt nur eine Warnung, weil u<0
immer falsch ist), und seine Ausführung liefert das erwartete Ergebnis.



Jörg Wunsch schrieb:
> Was ist am Typ void (*)(void) als generischem Funktionszeiger-
> typ denn anders als an void * als generischen Objektzeigertyp?

Lassen wir mal den Zeiger weg:

Keine Variable kann vom Typ void sein, aber eine Funktion sehr wohl vom
Typ void(void). Für generische Funktionszeiger müsste man in ähnlicher
Weise einen Typ einführen, den keine Funktion annehmen kann, bspw.
voidfunc. Dann könnten man voidfunc* als generischen Funktionszeigertyp
definieren.

> […] aber das ändert nichts daran, dass man selbigen durchaus per
> definitionem zu einem generischen Funktionszeiger machen kann, wenn
> man will.  Schließlich hatte C anfangs auch noch keine void *, und man
> hat char * als generischen Objektzeigertypen missbraucht.

Aber du schreibst ja selber, dass das Missbrauch war. Deswegen sollte
man diesen Fehler besser nicht wiederholen ;-)

von Rolf M. (rmagnus)


Lesenswert?

Jörg Wunsch schrieb:
> Rolf Magnus schrieb:
>> Und wie soll dieser Operator dann konkret aussehen? Es gibt keinen
>> generischen Funktionszeigertyp, in den es implizit konvertierbar wäre.
>> Das einzige wäre ein void (*)(void), der aber (aus verständlichen
>> Gründen) eine explizite Konvertierung erfordert.
>
> Warum?

Genau darum:

> OK, ein Objekt vom Typ void * kann man nie dereferenzieren, einen 
Funktionszeiger
> vom Typ void (*)(void) schon

Über diesen Zeiger könnte man die Funktion auf falsche Weise aufrufen. 
Und sowas sollte nicht komplett durch implizite Konvertierungen möglich 
sein. Wäre es das, dann würdest du dich vermutlich darüber beschweren, 
daß das viel zu gefährlich ist.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> Der Compiler darf alles optimieren, was er will. Aber: Der Endeffekt
> muss sein, dass sich die sichtbaren Auswirkungen so verhalten, als ob
> (as if) die Optimierung nie stattgefunden hätte.
Aber das ist doch auch so, der Wert (true) ändert sich nie, egal ob die 
Funktion implementiert wird oder nicht.
Der Compiler kann ja auch hier:
1
if(false) {
2
 printf("Hallo");
3
}
den Aufruf zu printf rauswerfen, und in der Folge doch auch der gLinker 
nicht mitbekommen das er ein solches Symbol suchen soll...?

von (prx) A. K. (prx)


Lesenswert?

Dieser Streit erinnert mich mittlerweile an die bekannte scholastische 
Frage, wieviel Engel auf einem Stecknadelkopf Platz finden.

von Sven P. (Gast)


Lesenswert?

Peter II schrieb:
> Läubi .. schrieb:
>> Weil per Definition ein Pointer auf 0 nicht initialisiert ist. Die '0'
>> ist also schon vergeben.
>
> nein, nicht per Definition. Sonst könnte man man nie den Ram ab adresse
> 0 verwenden.

Ich predige wieder: Zeiger sind keine Adressen.
Es gibt einen Zeiger, der (als Ganzzahl interpretiert) '0' ist. Der ist 
per Definition nicht initialisiert. Das hat aber mit den Adressen im 
Arbeitsspeicher nichts zu tun. Man kann trotzdem das 0. Byte im RAM 
benutzen. Der entsprechende Zeiger könnte (als Ganzzahl interpretiert) 
etwa '4711' sein.

von (prx) A. K. (prx)


Lesenswert?

Sven P. schrieb:

> Es gibt einen Zeiger, der (als Ganzzahl interpretiert) '0' ist. Der ist
> per Definition nicht initialisiert.

Doch, initialisiert ist er, sonst wäre er mit einiger Wahrscheinlichkeit 
nicht 0. Definiert ist, dass er auf nichts zeigt.

Dass der Wert 0 nicht die Adresse 0 sein muss ist korrekt. Mit den INMOS 
Transputern gab es eine 16/32-Bit Prozessorfamilie, deren Adressraum 
vorzeichenbehaftet war und im Layout effektiv bei MIN_INT anfing. Die 
Adresse 0 lag genau mitten drin und liess sich in der 16-Bit Version 
auch kaum vermeiden.

von Sven P. (Gast)


Lesenswert?

Yalu X. schrieb:
> Ein ähnlich gelagerter und etwas leichter zu durchschauender Fall ist
> übrigens folgender:
> [...]

Interessanterweise meldet die GCC da den erwarteten Fehler beim Linken, 
wenn man mit dem C-Compiler übersetzt...

Mit dem Zeiger haste Recht, so meinte ich das.

von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Sven P. schrieb:
>
>> Es gibt einen Zeiger, der (als Ganzzahl interpretiert) '0' ist. Der ist
>> per Definition nicht initialisiert.
>
> Doch, initialisiert ist er, sonst wäre er mit einiger Wahrscheinlichkeit
> nicht 0. Definiert ist, dass er auf nichts zeigt.
>
> Dass der Wert 0 nicht die Adresse 0 sein muss ist korrekt. Mit den INMOS
> Transputern gab es eine 16/32-Bit Prozessorfamilie, deren Adressraum
> vorzeichenbehaftet war und im Layout effektiv bei MIN_INT anfing. Die
> Adresse 0 lag genau mitten drin und liess sich in der 16-Bit Version
> auch kaum vermeiden.

Das hat aber alles mit Nullzeigern erstmal rein gar nichts zu tun. Der 
Nullzeiger muß nicht den Zahlenwert 0 haben. Der Wert dafür darf 
beliebig sein.

von (prx) A. K. (prx)


Lesenswert?

Rolf Magnus schrieb:

> Das hat aber alles mit Nullzeigern erstmal rein gar nichts zu tun. Der
> Nullzeiger muß nicht den Zahlenwert 0 haben. Der Wert dafür darf
> beliebig sein.

Yep, nix anderes hatte ich geschrieben. Nur ist eben nicht nur NULL zum 
Vergleich zulässig, sondern auch 0, selbst wenn ein dazu gleicher Wert 
des Pointers nicht wirklich 0 ist. Was einerseits eine gewisse Konfusion 
fördert, andererseits zu Überraschungen einläd, wenn jemand einen 
Pointer castet. Und manche Leute casten wie wild, auch wenns unnötig 
ist.

Allerdings hatte ich mir diesen Zirkus damals erspart und seelenruhig 
zugelassen, dass man mit entsprechend Pech einen gültigen Zeiger kriegt, 
der NULL ist. Zumindest bei der 16-Bit Plattform, die eher sowas wie ein 
Mikrocontroller war - und wer die nutzte wusste was er tat. Die 
32-Bitter hatten anno Dunnemal nicht genug RAM um jemals damit Ärger zu 
kriegen.

von Sven P. (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Das hat aber alles mit Nullzeigern erstmal rein gar nichts zu tun. Der
> Nullzeiger muß nicht den Zahlenwert 0 haben. Der Wert dafür darf
> beliebig sein.

Musst eben unterscheiden, gewissermaßen hat ein Zeiger ja zwei 
Zahlendarstellungen. Einmal diejenige, die man erlaubterweise erhält, 
wenn man den Zeiger als Ganzzahl interpretiert (dort ist der Nullzeiger 
nämlich '0') und einmal diejenige, die letztlich im Speicher steht.

Die Zuordnung zwischen den beiden ist nicht weiter spezifiziert, schrieb 
ich ja oben schon.


Nun, zum Ausgangsproblem: Warum meldet der Linker einen Fehler, wenn man 
als C übersetzt? Klar, weil das Symbol 'u' nicht exisitert. Und warum 
meldet der Linker keinen Fehler, wenn man als C++ übersetzt..?

von Rolf M. (rmagnus)


Lesenswert?

Sven P. schrieb:
> Rolf Magnus schrieb:
>> Das hat aber alles mit Nullzeigern erstmal rein gar nichts zu tun. Der
>> Nullzeiger muß nicht den Zahlenwert 0 haben. Der Wert dafür darf
>> beliebig sein.
>
> Musst eben unterscheiden, gewissermaßen hat ein Zeiger ja zwei
> Zahlendarstellungen. Einmal diejenige, die man erlaubterweise erhält,
> wenn man den Zeiger als Ganzzahl interpretiert (dort ist der Nullzeiger
> nämlich '0') und einmal diejenige, die letztlich im Speicher steht.

Du meinst, wenn man einen Zeiger in eine Ganzzahl konvertiert, nicht 
interpretiert.

> Nun, zum Ausgangsproblem: Warum meldet der Linker einen Fehler, wenn man
> als C übersetzt?

Unterschiedliche Compilerversionen? Bei mir ist es jedenfalls nicht der 
Fall. Sowohl als C, als auch als C++ kommt (übrigens sogar bei -O0) kein 
Fehler. Getestet mit gcc 4.6.1

Ich bin mir jetzt nur noch nicht so sicher, ob das tatsächlich nach 
ISO-Norm verboten ist. Denn es gilt zwar das:

Karl Heinz Buchegger schrieb:
> Der Compiler darf alles optimieren, was er will. Aber: Der Endeffekt
> muss sein, dass sich die sichtbaren Auswirkungen so verhalten, als ob
> (as if) die Optimierung nie stattgefunden hätte.

Aber die sichtbaren Auswirkungen ("observable behavior") sind definiert 
als I/O und der Zugriff auf volatile-Daten, und das einzige, was in die 
Kategorie fällt, ist die Ausgabe an cout, die sich durch die Optimierung 
ja tatsächlich nicht verändert.

von Sven P. (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Du meinst, wenn man einen Zeiger in eine Ganzzahl konvertiert, nicht
> interpretiert.
Ja.

>> Nun, zum Ausgangsproblem: Warum meldet der Linker einen Fehler, wenn man
>> als C übersetzt?
>
> Unterschiedliche Compilerversionen?
4.2.4 hier.
Ich muss allerdings korrigieren, habe mich um ein Gleichheitszeichen 
vertippt. Nun bringt auch der C-Compiler beim Linken keinen Fehler mehr:
1
extern unsigned u;
2
if (u >= 0) puts("hallo");


Mit flüchtigem 'u' aber dann doch wieder:
1
extern volatile unsigned u;
2
if (u >= 0) puts("hallo");

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.