StringLoader



  • Mox schrieb:

    Ich habe es jetzt mal ausprobiert, das hier reicht hin:

    std::wstring MyLoadString(UINT uID)
    {
        WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
        int len = LoadStringW(GetModuleHandle(nullptr), uID, wsz, 0);
        return std::wstring(*reinterpret_cast<PCWSTR*>(wsz), len);
    }
    

    Wenn ich das richtig verstehe, soll mit diesem Aufruf von LoadStringW ein Zeiger in den Puffer wsz geschrieben werden. Der aber bietet doch gar nicht genug Platz für einen Zeiger?!



  • tomy86 schrieb:

    Hat jemand eine Idee, warum das so ist?
    LG

    Ja, ich hätte da eine Idee. Nur so viel: Merke Dir einfach das gelieferte Objekt:

    std::wstring cls = MyLoadString(IDC_WINDOW_MAIN_CLASS_NAME);
    wc.lpszClassName = cls.c_str();
    


  • Belli schrieb:

    Der aber bietet doch gar nicht genug Platz für einen Zeiger?!

    Sondern? Wie viel fehlt denn?



  • WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
    

    Da sizeof(WCHAR) mindestens (oder immer?) zwei ist, sizeof(WCHAR*) aber die Größe eines Zeigers, dürfte mindestens (oder immer?) die Hälfte fehlen.
    Auf meinem 32bit - System ist der oben reservierte Puffer nur zwei Byte groß ...



  • Belli schrieb:

    Auf meinem 32bit - System ist der oben reservierte Puffer nur zwei Byte groß ...

    Du reservierst Speicher für 2 WCHAR, nicht BYTE! Und da sizeof(WCHAR) == 2, wie Du richtig festgestellt hast, hast Du Speicher für 2*2 Bytes reserviert.



  • Jo, und da passt kein Zeiger rein, wie ich hier:

    Belli schrieb:

    Wenn ich das richtig verstehe, soll mit diesem Aufruf von LoadStringW ein Zeiger in den Puffer wsz geschrieben werden. Der aber bietet doch gar nicht genug Platz für einen Zeiger?!

    angemerkt habe.

    Edit:
    Ne, Quatsch!
    Reserviert werden nur 2 Byte - und da passt kein Zeiger rein.

    Es wird ja:

    WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
    

    Größe eines Zeigers (auf WCHAR, was aber unerheblich ist, da ein Zeiger immer dieselbe Größe haben sollte) dividiert durch Größe eines WCHAR (= 2) reserviert, also Platz für einen halben Zeiger.



  • @Belli
    Sagen wir WCHAR ist 2 Byte gross.
    Sagen wir ein Zeiger ist 4 Byte gross.
    sizeof(WCHAR*) / sizeof(WCHAR) ist dann 2.
    Es werden also zwei WCHAR s (!!) reserviert, also 2 * sizeof(WCHAR) == 4 Byte, also genug Platz für einen Zeiger.
    Ist das wirklich so schwer zu verstehen?

    Anders wäre es wenn da

    char bufferForAPointer[sizeof(WCHAR*) / sizeof(WCHAR)]; // ACHTUNG, FALSCH!!!
    

    stehen würde.
    Tut's aber nicht.
    Weil die Funktion ja einen WCHAR -Puffer erwartet.



  • Sorry, ich hatte ein Brett vorm Kopf! Ich hab immer nur die 2 in den [] gesehen ... Klar, der Datentyp ist ja WCHAR, und somit selbst schon 2 Byte groß ...

    Asche auf mein Haupt!

    Aber warum macht man das so ... mhm ... umständlich, und nicht einfach

    WCHAR *wsz;
    

    ?
    Ah ja, dann stimmt der Datentyp für die Funktion nicht mehr ... mhm, manchmal lohnt es sich doch, genauer hinzusehen ...



  • @Mox
    Bitte nicht reinterpret_cast für sowas verwenden.
    Dass ich hier memcpy verwendet habe hat durchaus einen (guten) Grund.
    Nämlich dass memcpy hier standardkonform ist, reinterpret_cast dagegen nicht.

    Und zwar weil reinterpret_cast hier einerseits strict-aliasing verletzt (mit MSVC AFAIK derzeit egal, aber auf sowas sollte man sich nicht verlassen), und andrerseits weil man damit ein Alignment-Problem bekommen könnte.



  • Oh ja, an Alignment habe ich in der Tat keinen Gedanken verschwendet, da hast hast Du natürlich absolut recht! Aber memcpy sieht immer so teuer aus. 😉

    Dann eben so:

    std::wstring MyLoadString(UINT uID)
    {
        union
        {
            WCHAR wsz[sizeof(WCHAR*) / sizeof(WCHAR)];
            WCHAR* pwsz;
        };
    
        int len = LoadStringW(GetModuleHandle(nullptr), uID, wsz, 0);
        return std::wstring(pwsz, len);
    }
    


  • Immer noch nicht standardkonform fürchte ich.
    Nimm memcpy .
    Der Compiler optimiert das vermutlich eh komplett weg.



  • hustbaer schrieb:

    Immer noch nicht standardkonform fürchte ich.

    Weil? Alignment kann es ja nun nicht mehr sein.



  • Naja, ist vielleicht ne Grauzone.

    Vorweg: ich mein(t)e mit "nicht standardkonform" natürlich dass das Programm laut Standard "undefined behavior" (UB) hat.

    Du übergibst nen WCHAR* auf wsz und LoadStringW schreibt da was rein.
    Dann liest du aus pwsz was raus.
    Würdest du direkt was nach wsz schreiben wäre es deswegen UB, weil du ne Union nur als das lesen darfst als was sie zuletzt beschrieben wurde.

    Wäre LoadStringW in deinem Programm implementiert, und würde direkt den übergebenen Zeiger dereferenzieren um reinzuschreiben (also ohne memcpy oder "selbst mittels char -Zeiger kopieren"), dann wäre es zusätzlich nochmal UB wegen einer strict aliasing Verletzung*.

    Jetzt ist LoadStringW natürlich extern implementiert, und man könnte argumentieren dass der Compiler daher ja nicht wissen kann wie LoadStringW den Wert schreibt, und es daher OK sein muss. Allerdings übergibst du an LoadStringW nen Zeiger auf wsz , und nicht einen auf pwsz . Daher könnte man genau so argumentieren dass LoadStringW auch nur wsz beschreiben kann.
    Leider reichen meine Kenntnisse des Standard nicht aus um mich hier weiter festzulegen. Ich vermute dass klar aus dem Standard hervorgeht ob es OK ist oder nicht. Aber selbst wenn es OK sein sollte, würde ich den Code lieber mit memcpy schreiben. Einfach weil es mit memcpy sicher OK ist, und ich keinen guten Grund sehe es ohne memcpy zu schreiben.

    * Die genaue strict-aliasing Regel bitte selbst nachzulesen. Hab nicht alle Details exakt im Kopf, und diese Regel ist mir auch etwas zu heikel als dass ich versuchen würde sie mal eben schnell vereinfacht zusammenzufassen.



  • hustbaer schrieb:

    Würdest du direkt was nach wsz schreiben wäre es deswegen UB, weil du ne Union nur als das lesen darfst als was sie zuletzt beschrieben wurde.

    Naja, dann eben doch den Cast, das ist doch ganz egal. Die Union stellt doch trotzdem sicher, dass die Ausrichtung passt.

    Ansonsten wird mir das hier deutlich zu esoterisch. Wenn das UB ist, dann bitte sehr. Das nehme ich gerne und bewusst in Kauf (noch). Ich arbeite hier viel zu viel mit ganz kleinen Systemen, als das ich mir das nutzlose Kopieren von Daten leisten kann und will. Und wegen irgendwelcher Phantome, die sich zu zudem nicht einmal richtig beim Namen nennen lassen und sich vielleicht am 03.03.33 auswirken, werde ich in dieser Richtung sicherlich nichts ändern.

    Das ist natürlich nur meine Meinung, kann selbstverständlich auch völliger Scheiß sein. Aber solange ich nicht weiß was genau das Problem ist und nicht eine einzige Implementierung kenne, die darüber tatsächlich stolpert...



  • @Mox
    Strict aliasing Verletzungen sind mit Compilern wie GCC oder Clang IIRC gar kein "esoterisches" Problem das sich nicht auswirkt, sondern durchaus real.

    Was das "unnötige Kopieren" angeht: probier doch einfach mal aus was dein Compiler für ein fixed-size 4 oder 8 Byte memcpy (mit Optimierungen) für Code generiert. Normalerweise sollte davon nichts mehr übrig bleiben. (Ausser Source oder Target sind nicht lokale Variablen, dann wird ein Load oder Store oder evtl. sogar ein Load UND ein Store übrig bleiben - das selbe was mit ner Union oder reinterpret_cast auch gebraucht wird.)



  • Gut, wir haben also ein reales Problem. Darf ich fragen, was genau das Problem ist?

    Davon ab: Ich habe solche und solche Compiler im Einsatz. Nicht alle optimieren gleich gut. Von daher wollte ich nicht so viel Energie darauf verschwenden, praktisch nicht existierende Probleme zu lösen.





  • Frage: "•Is there any way to work around this problem?"
    Antwort: "You could use a union to represent the memory you need to reinterpret"



  • Kommentar zur akzeptierten Antwort:

    Just a warning, that it's unspecified behaviour to use an union by writing to one member and reading from another, according to the C++ standard. So it may work, it may not.

    Bitte auch beachten dass C++ hier andere Regeln als C hat.
    Bei C ist der Umweg über ne union offiziell OK. Mit C++ ist er offiziell (=laut Standard) nicht OK. Wobei viele (alle?) C++ Compiler es trotzdem unterstützen.



  • Also mMm. beste Option ist memcpy .
    Wenn du memcpy unbedingt vermeiden willst nimm ne union .
    Auf jeden Fall nicht reinterpret_cast .


Anmelden zum Antworten