Ausgabe von char *name über cout ?



  • Es gibt einen ganz klaren objektiven Fakt: der Standard überlässt das Verhalten von reinterpret_cast weitestgehend der Implementierung. D.h., der Cast von char* zu void* könnte bei einer standardkonformen Implementeirung auch z.B. immer 0 liefern. Oder immer 0x123456. Oder das binäre Komplement des Originalzeigers. Die Implementierer sind lediglich angehalten, das Verhalten umzusetzen, das der Nutzer generell erwartet.
    Bei static_cast hingegen ist das Verhalten definiert.

    Darüber hinaus gibt es noch stilbezogene Gründe (reinterpret_cast sollte man wenn immer möglich vermeiden), die will ich jetzt aber nicht groß aufführen. Und im Übrigen kann man die Aussage, dass static_cast "mächtiger" als reinterpret_cast wäre, eigentlich nicht stehen lassen. Beide Cast unterscheiden sich vorwiegend in den erlaubten Konvertierungen und reinterpret_cast übernimmt im Allgemeinen das "Low-Level-Zeug", was man sehr selten wirklich braucht. Außerdem suggeriert die Aussage in dem Fall, dass static_cast langsamer wäre, weil da mehr Logik drin sei. Das stimmt aber nicht.



  • Naja, wie wird denn laut Standard nun ein static_cast von char* auf void* definiert? Kann mir gar nicht vorstellen, dass hier nicht genauso vage auf eine Implementationsabhängigkeit verwiesen wird.

    Mit Mächtigkeit wollte ich zudem nie ausdrücken, dass der cast "langsamer" wäre, das wurde mir in den Mund gelegt.



  • char* auf void* ist so definiert, dass das Zurückcasten des void*-Zeigers in einen (cv) char*-Zeiger wieder den Originalzeiger ergibt. Wie schon erwähnt ist das bei reinterpret_cast nicht der Fall. Da man aber void*-Zeiger generell zur typenlosen Übertragung von Objekten in Schnittstellen nimmt und nicht unbedingt für die Ausgabe des Zeigerwerts, ist das genau die Eigenschaft, die man braucht.



  • Decimad schrieb:

    Naja, wie wird denn laut Standard nun ein static_cast von char* auf void* definiert? Kann mir gar nicht vorstellen, dass hier nicht genauso vage auf eine Implementationsabhängigkeit verwiesen wird.

    Sollte aus irgendeinem Grund ein T* anders als ein U* sein, wäre static_cast korrekt und reinterpret_cast falsch. Denken wir zB an far Pointer oder eeprom Pointer etc. Das sind andere Zeiger als "normale" zB near Pointer oder ram pointer. Hier wäre ein reinterpret_cast falsch und würde zu falschem Code führen während ein static_cast den Fehler Melden würde oder die Konvertierung korrekt machen würde (je nachdem um was für unterschiedliche Zeiger es sich hier handelt).

    Deshalb ist reinterpret_cast hier falsch. Es tut hier zufällig das was du willst, aber du kannst das nicht generalisieren. static_cast ist hier in jeder situation korrekt, reinterpret nur in einigen.

    reinterpret sagt ja: "trust me on this one" - sprich der compiler soll einfach tun und nicht denken. Dh wir deaktivieren hier absichtlich Sicherheitsmechanismen. Und wofür?



  • cout << (void*)name;
    

    spart sogar tipparbeit :p



  • C-Stil ftw schrieb:

    cout << (void*)name;
    

    spart sogar tipparbeit :p

    Das kann aber leicht nach hinten losgehen:

    struct base {};
    struct derived : base {};
    struct not_derived {};
    
    base *b;
    (derived*)b;      // das selbe wie static_cast
    (not_derived*)b;  // das selbe wie reinterpret_cast
    
    base *const b2;
    (not_derived*)b2; // eine Mischung aus const_cast und reinterpret_cast
    

    Der Schreibweise allein kann man also nicht ansehen, welcher Cast tatsächlich ausgeführt wird.



  • C-Stil ftw schrieb:

    cout << (void*)name;
    

    spart sogar tipparbeit :p

    Entspricht in etwa einem reinterpret_cast mit allen negativen Konsequenzen!



  • So, jetzt mal tacheles hier. Die ursprüngliche Aufgabe war es, den Wert eines Pointers to char auf der Konsole auszugeben. Ich war mir bewusst, dass unterschiedliche Plattformen evtl. abweichende Zeigerrepräsentationen bieten. Es ist schön und gut, dass static_cast garantiert, dass typ* -> void* -> typ* gilt, allerdings ist es nicht das, was ich hier möchte. Ich möchte nämlich den char* ausgeben, kein void* der nach einem weiteren cast wieder sicher zu einem char* würde. Daher habe ich den cast gewählt, von dem ich am wenigsten erwarten würde, dass er irgendeine implizite konvertierung durchführen würde, in diesem Fall ein reinterpret_cast.
    Keinesfalls ist es als Zufall zu betrachten, dass der entsprechende Quelltext das tut, was er tut. Es wäre eher Pech, wenn er das nicht tut.
    Die Anforderung war zu keinem Zeitpunkt, dass man den Zeiger hinterher noch sicher dereferenzieren kann oder ähnliches. Es ging darum, bei der Ausgabe so nah wie möglich am Originalwert zu bleiben, ich versprach mir durch einen reinterpret_cast genau das.

    Eure Angriffe nach dem Motto "indiskutabel" auf mein "Grundwissen" haben mich schon etwas enttäuscht, ich bin hier noch niemanden auf eine solche Art und Weise persönlich angegangen, bei dem ich meinte, ein Gewisses Interesse zu verspüren.



  • Decimad schrieb:

    ! Vielleicht könnt ihr das ja auch verpixeln, damit bloß niemand die Buchstabenkombination lesen kann 😉

    warum? das ganze per reinterpret_cast<void*> zu verändern, dass wir nur addresse sehen können reicht doch 😉 😃



  • Decimad schrieb:

    So, jetzt mal tacheles hier. Die ursprüngliche Aufgabe war es, den Wert eines Pointers to char auf der Konsole auszugeben. Ich war mir bewusst, dass unterschiedliche Plattformen evtl. abweichende Zeigerrepräsentationen bieten. Es ist schön und gut, dass static_cast garantiert, dass typ* -> void* -> typ* gilt, allerdings ist es nicht das, was ich hier möchte.

    Doch genau das möchtest du. Denn wenn das nicht gilt, dann ist der Wert von dem void* ja falsch.

    Wenn der char* zB ein eeprom Zeiger wäre und void* ein ram Zeiger, wäre die Adresse die du ausgibst schlicht falsch.

    Wenn A->B->A nicht gilt, dann ist die Konvertierung A->B oder B->A falsch. Wenn A->B falsch ist, ist B falsch. Und genau das Problem hast du hier. Sollte B->A das Problem sein wäre der Code korrekt.

    Nur du kannst nicht garantieren dass wenn A->B->A nicht gilt, dennoch A->B gilt.

    Deshalb brauchst du hier einen static_cast.



  • Aber ich wollte doch einen char* ausgeben! Das war doch der ganze Punkt. Die iostream-Bibliothek unterstützt das auf herkömmlichem Wege nicht. Dafür verwende ich sozusagen den operator<< für void* auf falsche Art und Weise (hierüber hat sich aber noch gar niemand aufgeregt!). Wenn ich einen char* ausgeben möchte, dann interessiert mich gar nicht, wie er nach Konvertierung zu einem void* aussähe. Mir ist klar, dass der Wert in einem Fall von unterschiedlichen Repräsentationen Typ-gesehen falsch wäre (zumindest erwartete ich das, reinterpret_cast könnte natürlich, wie angemerkt wurde, in diesem Fall dasselbe tun wie static_cast), aber genau das möchte ich ja herbeiführen, weil ich einen char* ausgeben wollte, keinen void*!



  • Was ich aber beim Betrachten des Quelltextes "meiner" iostream-Bibliothek sehe, ist, dass hier nur für void* ein Operator festgelegt ist. Darf ich daraus schließen, dass die iostream-Bibliothek die void*-Repräsentation von allen Pointern ausgeben muss, oder ist das in "meiner" Bibliothek nur daher so implementiert, weil es eben keine unterschiedlichen Repräsentationen gibt?



  • Ist m.E. auch laut Standard so. Offenbar sah man das als ausreichend. Es gibt ja auch eine implizite Konvertierung von T* zu void* (die nur bei char* wegen der Extra-Überladung nicht berücksichtigt wird). Und da hast du ja schon wieder eine Bestätigung: rate mal, welchen Cast der Compiler dafür implizit nimmt und welcher somit nach Meinung der Compiler- und Libraryhersteller der Richtige ist.



  • Decimad schrieb:

    Aber ich wollte doch einen char* ausgeben! Das war doch der ganze Punkt. Die iostream-Bibliothek unterstützt das auf herkömmlichem Wege nicht. Dafür verwende ich sozusagen den operator<< für void* auf falsche Art und Weise (hierüber hat sich aber noch gar niemand aufgeregt!).

    Weil das vollkommen OK ist wie die iostream library das macht.

    Wenn ich einen char* ausgeben möchte, dann interessiert mich gar nicht, wie er nach Konvertierung zu einem void* aussähe.

    Was dich interessiert ist die adresse. Stellen wir uns vor der char* zeigt ins eeprom. An die Adresse 0xaabb. das sind 2 Byte. void* ist aber nicht ein zeiger ins eeprom sondern ins Ram und dort haben wir 4 byte. dann reinterpretieren wir aus den 2 byte plötzlich 4 byte. das bedeutet da kommen ja zusätzliche 2 byte rein. Ergo ist die Ausgabe falsch. Denn es werden 4 byte ausgegeben, obwohl es nur 2 byte sein dürften.

    Und genau dann hast du mit reinterpret ein Problem. es ist ja nicht so als ob reinterpret hier nicht funktionieren würde, es tut nur etwas anderes als es tun sollte. In den meisten Fällen tut es zwar das falsche aber mit korrektem Ergebnis. Blöd wirds nur, wenn das Ergebnis irgendwann nicht mehr korrekt ist.

    Und deinen 2. Post verstehe ich leider nicht.
    der operator<< für void* gibt die adresse aus. Der für char* den string.



  • Na dass der Compiler da irgendeine vergleichbare Form des static_cast's verwenden muss, ist mir ja klar!
    Ich hatte nur nicht erwartet, dass die void*-Repräsentation hier das Standardverhalten auf allen Plattformen für alle Zeigertypen sein soll. Wenn ich also Zeiger ausgebe, dann sagen die mir die Werte dort vielleicht nicht besonders viel, bevor ich sie nicht wieder in ihren Adressraum zurücktransformiere.



  • So, jetzt habe ich im Standard gefunden:

    std 3.9.2.4 schrieb:

    ...
    A void* shall be able to hold any object pointer. A cv-qualified or cv-unqualified (3.9.3) void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified char*.

    Das heißt für mich also, dass die Diskussion gerade für den Typ char* sowieso hinfällig war 😃



  • Das ändert nichts an meinen Beispielen mit eeprom, near, far, etc. pointer. Denn dort ist die Repräsentation ja eben nicht gleich. Davon rede ich ja die ganze Zeit 😕 Solange void* und char* eine identische repräsentation haben ist reinterpret ok - sobald sie sich ändert ist nur noch static_cast OK.



  • Also wahrscheinlich interpretiere ich jetzt falsch, was dort geschrieben steht, wenn es so deute, dass die Konversation char* -> void* laut standard zumindest keine "Größen"änderung beinhaltet. Aber die zitierte Aussage lässt noch zu, dass die Adresse von unterschiedlichen physikalischen Adressbereichen noch eine Wandelung bei Konvertierung zum void* durchläuft (Also von einem x-Bit-Wert zu einem anderen x-Bit-Wert)?



  • Im Standard gibt es auch keine eeprom, far, near, etc. Zeiger. Die gibt es nur in der Praxis. Denn es ist von der Hardware her nicht sinnvoll alle Zeiger gleich zu behandeln.

    Das ist ja nur ein konkretes Beispiel wo reinterpret hier wirkliche real world Probleme macht.

    PS:
    aber ja, wie schon gesagt: solange der char* und der void* die selbe breite haben wird der Code funktionieren. Er funktioniert aber nur zufällig. Wenn du zB einen functionszeiger nehmen würdest statt dem char* wäre der reinterpret falsch.



  • Somit stellt sich mir ja nur noch eine Frage: Wie gebe ich denn nun Zeiger aus, ohne dass sie vorher in den fiktiven "void*"-Raum transformiert werden?


Anmelden zum Antworten