Datum:
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/
#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; } |
Gruäss Simon Ups, gerade gesehen, dass der Threadtitel falsch ist...
Datum:
Hab's gerade kapiert! :-)
Datum:
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.
Datum:
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?
Datum:
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?
Datum:
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äääää?
Datum:
Ich habe es gerade mal unter VC++ 10 ausprobiert; Der Linker gibt die erwartete Fehlermeldung aus.
Datum:
warning: the address of 'std::string middle()' will always evaluate as 'true' [-Waddress]
Datum:
GCC compiliert es problemlos, und die Ausgabe ist: This space has been intentionally left blank: ->1<-
Datum:
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)?
Datum:
Du musst die Option -Wall setzen.
Datum:
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:
std::string x = middle; |
Wenn middle ein String wäre, würde das gehen. Die Fehlermeldung ist.
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.)
Datum:
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.
Datum:
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.
Datum:
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).
Datum:
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.
Datum:
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.
Datum:
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?
Datum:
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!?!
Datum:
> 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.
Datum:
Du solltest aber beachten, dass auch std::string nichts mit dem Sprachdesign zu tun hat, sondern Bestandteil einer Bibliothek ist.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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. :-)
Datum:
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.
Datum:
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.
Datum:
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
Datum:
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?
Datum:
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.
Datum:
Damit da keine Missverständnisse aufkommen. 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. Compiler/Linker hätten hier überhaupt kein ausführbares Programm erzeugen dürfen.
Datum:
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?
Datum:
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.
Datum:
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.
Datum:
Ganz so einfach ist es nicht, es wird ja auf den Bezeichner "middle" zugegriffen. Es bleibt ein Implementierungsproblem.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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:
#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; } 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.
Datum:
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! *********************************************************************
Datum:
Ist die Funktion middle() definiert, sollte ein vernünftige Implementierung die Adresse der Funktion ausgeben.
Datum:
Jörg Wunsch schrieb: > Naja, dann nimm das hier: > >
> #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; > } > > std::string middle(void) { return "0"; } > |
> > 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.
Datum:
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.
Datum:
welche ausgabe schrieb: > Ist die Funktion middle() definiert, sollte ein vernünftige > Implementierung die Adresse der Funktion ausgeben. Nein.
Datum:
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.
Datum:
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.
Datum:
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.)
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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:
#include <iostream> extern unsigned int u; int main() { if (u<0) std::cout << "negativ" << std::endl; else std::cout << "nichtnegativ" << std::endl; return 0; } |
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 ;-)
Datum:
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.
Datum:
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:
if(false) { printf("Hallo"); } |
den Aufruf zu printf rauswerfen, und in der Folge doch auch der gLinker nicht mitbekommen das er ein solches Symbol suchen soll...?
Datum:
Dieser Streit erinnert mich mittlerweile an die bekannte scholastische Frage, wieviel Engel auf einem Stecknadelkopf Platz finden.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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..?
Datum:
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.
Datum:
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:
extern unsigned u; if (u >= 0) puts("hallo"); |
Mit flüchtigem 'u' aber dann doch wieder:
extern volatile unsigned u; if (u >= 0) puts("hallo"); |