Wie arbeitet std::to_string?

  • C++

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von rwCapt.

    Wie arbeitet std::to_string?

    Guten Abend!

    Ich finde die std::string-Klasse ziemlich interessant, da sie sehr flexibel ist und gefühlt unendlich viele Einsatzmöglichkeiten bietet. Mal eben schnell eine Edit Box nach einem Text durchsuchen? std::string::find! Wobei man hier wohl eher den wide-string benutzen sollte. Wie dem auch sei. Mich interessiert, wie to_string arbeitet. Ich habe mal versucht, selbst eine Konvertierungsfunktion zu schreiben und dies mit einer for-Schleife gemacht, in der ich einen char so lange hoch zähle, bis er die gegebene int-Zahl erreicht hat.

    Ich kann mir kaum vorstellen, dass std::to_string das genauso macht. Weiß vielleicht einer genauer darüber Bescheid? Man wird ja im Memory irgendwo die Zahl in Binärschreibweise liegen haben. Ich glaube, dass int direkt binär abspeichert. Also die Zahl 12 würde dann als "0000 1100" abgespeichert werden. Würde man die Zahl 12 (12'xx') als char anzeigen lassen, bekäme man nicht die 12, sondern den Charakter, mit dem Code 12. Und da ist ja das Problem. Wie bekommt man den int-Wert als char(xx'12') interpretiert. Dafür bieten C und C++ zahlreiche Konvertierungsmöglichkeiten, aber damit versteht man als Amateur nicht, was da im Hintergrund abläuft.

    Ich hoffe, es kann da jemand Licht ins Dunkle bringen :saint:

    Edit: Das muss doch irgendwie mit Schleifen gemacht werden. Wenn man mal überlegt, wie Literale abgelegt werden, könnte man darauf schließen. Literale werden in der .data-Sektion abgelegt (ich hoffe ich habe die richtige Sektion erwischt). Kopiert man einen Literal bspw. als const char* in einen char*, wird auch dieser im Klar-text- irgendwo im Binärcode angezeigt.
    Auch integer-Literale werden im Klartext in der .data-Sektion abgelegt, dieses mal aber nicht als Text, sondern als binäre Zahl. Man kann also nur einen Zahlenwert in einen Text umwandeln, indem man diesen Zahlenwert Schritt für Schritt auseinander nimmt (bspw. mehrfach durch 10 teilt), um nach und nach auf die einzelnen Ziffern zu kommen und diese dann als char abzuspeichern.
    Bliebe nur noch die Frage, wie stringstream arbeitet. Wenn man bspw. eine HWND hWnd Adresse (völlig banales Beispiel) in einen string umwandeln möchte (der dann ja irgendwas wie "0x123456789" beinhalten würde), müsste der stringstream erkennen, dass es sich nicht um eine reine Zahl handelt (sondern um eine Hexadezimal mit dem Präfix 0x vorne dran). Aber vielleicht lässt es sich ja irgendwie erkennen, ob etwas nur eine Pointer-Adresse ist. Ob auch hier mit Schleifen gearbeitet wird? Schlägt das nicht arg auf die performance, wenn man einen ellenlangen input hat? Ich habe noch nie ausprobiert, einen stringstream mal mit etwas sehr großem zu füttern und zu schauen, wie lange die Umwandlung insgesamt dauert. Wobei man wohl nie eine so riesige Zahl haben kann, sodass eine Schleife ewig ackern müsste. Man könnte es ja auch so staffeln, dass man prüft, ob die Zahl ziemlich hoch ist, um dann nicht nur durch 10 sondern vielleicht durch >100 zu teilen. Alle andere inputs werden wohl über Überladungen abgefangen und dann anders behandelt. Wenn ich schon etwas im char-Format habe, brauche ich es ja nicht mehr umzuwandeln.

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „rwCapt“ ()

    Moin,

    das kannst du recht einfach herausfinden. Erstelle in Visual Studio ein C++ Projekt, includiere <string>, danach musst du evtl. einmal kompilieren. Wenn du nun im Projektmappen-Explorer, den Filter "Externe Abhängigkeiten" aufklappst, kannst nach den Dateien string und string.h suchen und diese anschauen.

    Die Natur ist bekanntermaßen knallhart, sie sortiert aus was sich nicht bewährt hat.(Harald Lesch, 2021)

    Demnach müssten wir bald dran sein...
    Danke! Ich war so pessimistisch, dass ich nicht in der Header-Datei geguckt habe. Da kann man sogar Haltepunkte platzieren und schauen, was passiert. Interessant! Pessimistisch deswegen, weil der eigentliche Funktionscode ja meist in einer lib versteckt ist. Hier anscheinend nicht.
    Jetzt wäre nur noch interessant zu wissen, was sprint_f im Hintergrund macht. Übergeben wird einmal der Output-Buffer, die Größe der zu formatierenden Zeichenkette, das Format ("%f") und der oder die eigentlich(en) zu formatierende(n) Wert(e).

    Auszug aus dem Header <string>

    C-Quellcode

    1. _NODISCARD inline string to_string(double _Val) { // convert double to string
    2. const auto _Len = static_cast<size_t>(_CSTD _scprintf("%f", _Val));
    3. string _Str(_Len, '\0');
    4. _CSTD sprintf_s(&_Str[0], _Len + 1, "%f", _Val);
    5. return _Str;
    6. }


    printf scheint ja eine Systemfunktion zu sein. Ich kann mir nicht vorstellen, wie diese Funktion arbeitet. Greift sie vielleicht sogar irgendwie auf Assembler-Code zu, um ihre Aktionen auszuführen? Es bliebe immer noch das Problem, dass ein int, float oder double im Binärformat abgelegt sind, während das für die Umwandlung in eine Zeichenkette halt übersetzt werden muss.
    Das solltest du in stdio.h herausfinden können.

    Noch ein guter Tipp, wie du schnell was finden kannst, schliesse alle offenen Dateien. Dann drücke STRG+F, wenn alle Dateien zu sind, öffnet sich ein anderes Suchfenster(nicht das kleine oben rechts), dort die CheckBox Externe Element einschliessen checken und auf Alle suchen klicken.



    Die Natur ist bekanntermaßen knallhart, sie sortiert aus was sich nicht bewährt hat.(Harald Lesch, 2021)

    Demnach müssten wir bald dran sein...

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Takafusa“ ()

    Ich habe mal versucht, mir die Ausführungsschritte mit Haltepunkten anzuzeigen. Das wesentliche wird da leider irgendwie übersprungen, sodass die Umwandlung in den Buffer plötzlich stattgefunden hat :rolleyes:

    Ich habe mir das ganze auch noch mal in Ghidra angeguckt und komme zu dem Entschluss, dass sprintf_s (und seine verlinkten Funktionen) einfach nur eine Umrechnung der Hexadezimalzahlen durchführt. Letztlich landet man hier: C:\Windows\WinSxS\wow64_microsoft-windows-ucrt_31bf3856ad364e35_10.0.19041.789_none_93e6eb93accdac11\ucrtbase.dll, wo wohl vieles ausgeführt und berechnet wird. Ich weiß jetzt, dass Datentypen letztlich nur dafür da sind, die binären Daten aus dem memory entsprechend zu interpretieren. Dieser Zusammenhang war mir bis dato gar nicht so bewusst.

    Die im Anhang gezeigte Funktion befindet sich wohl in der cpp (Danke für den Tipp mit dem Suchfenster), die den Inhalt der ucrtbase-dll darstellt. Eine Zeile ist hier recht interessant "int const result = processor.process();". Es wird auf einen processor_type verwiesen, was damit zusammenhängen kann, dass auf unterschiedlichen Systemen unterschiedlich umgerechnet wird (wobei ich mich frage, ob der Hexadezimalcode immer so unterschiedlich ist). result geht als return zurück an den caller. Eine kurze Google-Suche bekräftige die Vermutung, dass sich float und int "ganz einfach" umrechnen lassen. Der Datentyp float rechnet ja die Gleitkommazahlen in einen Hexadezimal-Äquivalent um, somit vermute ich, dass man das ganze auch wieder zurück rechnen kann.

    Bilder
    • TestKonsole - Microsoft Visual Studio.png

      94,8 kB, 1.285×1.510, 24 mal angesehen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „rwCapt“ ()

    rwCapt schrieb:

    dass Datentypen letztlich nur dafür da sind, die binären Daten aus dem memory entsprechend zu interpretieren


    So verhält es sich auch mit Klassen, Strukturen usw. welche nicht nur Vorteile beim schreiben/warten etc. bringen, für mich ist alles nur ein Datenstrom, den du nach belieben interpretieren kannst wie auch manipulieren.

    Daher finde ich es auch wichtig als Anfänger mit Option strict On in vb.net zu arbeiten. Sollte MS endlich mal von Haus aus auf On stellen.
    Die Natur ist bekanntermaßen knallhart, sie sortiert aus was sich nicht bewährt hat.(Harald Lesch, 2021)

    Demnach müssten wir bald dran sein...
    Ja, aber ich glaube, dass jeder so anfängt. Erst mal wird vieles versucht, einfachste Dinge ausprobiert, viele wagen sich sogar schon an größere Dinge ran und vergessen dann dabei, dass sie das System der Datentypen noch gar nicht richtig verstanden haben.
    Ich würde fast sagen, dass jeder schon mal versucht hat, einen Integer in einen String zu hämmern. Das ist ganz normal. Man muss nur irgendwann lernen, dass das so nicht korrekt ist ^^

    rwCapt schrieb:

    printf scheint ja eine Systemfunktion zu sein. Ich kann mir nicht vorstellen, wie diese Funktion arbeitet. Greift sie vielleicht sogar irgendwie auf Assembler-Code zu, um ihre Aktionen auszuführen?
    na, das hoffe ich doch.

    rwCapt schrieb:

    Es bliebe immer noch das Problem, dass ein int, float oder double im Binärformat abgelegt sind, während das für die Umwandlung in eine Zeichenkette halt übersetzt werden muss.
    Ist nicht dein Problem. Vertrau darauf, dass es in den cpp-Bibliotheken in allerhöchster Qualität gelöst ist.
    Was ResourcenSparsamkeit, Korrektheit und v.a. Performance angeht.

    rwCapt schrieb:

    Ich würde fast sagen, dass jeder schon mal versucht hat, einen Integer in einen String zu hämmern.
    Ich nicht.
    Ich hab schon oft das Rad neu erfunden, aber auf den Bolzen bin ich noch nicht gekommen. ;)
    Man kann das prima auf Google verfolgen, was eine Funktion für welchen Zweck hat.
    Das sage ich nicht weil man all das selbst auf Googke hätte rausfinden können, sondern weil es dir einiges an Leid erspart.
    Ich finde vorallem das cppreference sehr hilfreich in diesen Dingen ist, aber es gibt auch Platform bedingte Funktionen wie eben `_scprintf` welche dann in ebtsprechenden Docs nachgeschlagen werdenn können. (und damit meine ich, dass du deinen Computer schlagen solltest wenn er dir nicht hilft)

    Mal kurz so nebenbei: printf Funktionen formatieren eine Kette an Objekten so das sie dem vorangehenden String entsprechen, welcher diese mit speziell ausgewerteten Sequenzen (wie %f oben) in den übergebenen, Buffer (auch Funktionsintern wie z.B. stdout) einsetzen.

    Auch interessant zu Wissen: Wenn es darum geht eine Zahl in einen code point zu verwandeln, kann man dies auch durch einfache Addition machen.
    In ASCII sähe das so aus: 48 + Ziffer
    48 hier repräsentiert den ASCII code point für '0', wenn du nun zb 0 addierst, bleibt es null, wenn du nun 1 hinzufügst wird es zu 49, was stellvertretender ASCII codepoint für '1' ist.
    Geh nur nicht darüber!

    Des weiteren (tut mir leid wenn ich nicht zitiere, aber auf dem Smartphone ist das der blanke Horror), ja sprintf hat vieles mit Assembly zu tun.
    Aber alles in C/C++ hat mit assembly zu tun, da alles in Assembly übersetzt wird vom Compiler.
    Das was du meintest nennt sich `compiler intrinsics`, das sind Hauseigene Funktionen des Compilers, die von Compiler zu Compiler unterschiedlich sind.

    Und next, Datentypen sind für den Compiler nichts anderes als Größenangaben und Instruktionshinweise.
    Der Datentyp bestimmt wie viele Addressen der stack frame pointer überspringt um rückwirkend genug Platz auf dem Stack zu haben.
    Für int auf win64 wäre das 32 bits, heißt es springt 4 Adressen. (wenn die Variable nicht auf 64 bit aufgewertet wurde, andere Geschichte)
    Außerdem werden dem Compiler auch gewisse Instruktionen für unterschiedliche Datentypen hingelegt.
    Für integer könnte es 'add' sein, während für FP Typen es ganz andere sind und vermutlich auf manchen Architekturen auch mehrere.

    Zu deinen Konvertierungsängsten, im Endeffekt ist ein string nur ein Array von chars, das heißt es sind halt einfach mehrere Zusammenhängende 8-bit Ganzzahlenwerte.
    Das Konzept von Zeichen gibt es im engeren Sinne nicht, deine Architektur sieht nur codepoints.
    Ich habe dir auch oben beschrieben wie du Zahlen on codepoints einfach konvertieren kannst, das geht im übrigen auch mit Buchstaben, nur das du die Buchstaben mit Zahlenwerten ersetzen solltest.
    Z.B. +1 für B, 2 für C et cetera, und der Startwert ist natürlich auch anders, aber dafür gibt es ja in der Bildersuche genug Bilder über die ASCII Werte.
    ----------------------------------------------------------------------------------------------------------------------

    Premature optimization is the root of all evil.
    ~ Donald Knuth

    1. “There are only two kinds of languages: the ones people complain about and the ones nobody uses.
    2. “Proof by analogy is fraud.
    ~ Bjarne Stroustrup

    ----------------------------------------------------------------------------------------------------------------------

    ErfinderDesRades schrieb:


    Ich hab schon oft das Rad neu erfunden, aber auf den Bolzen bin ich noch nicht gekommen. ;)

    Das denke ich mir auch immer wieder, wenn ich vor einem Problem in C++ stehe, die Lösung gedanklich vor Augen habe, sie niederschreibe und dann später mal merke, dass es dafür bereits eine vordefinierte Standartfunktion gibt. ;)

    Das mit der Google-Suche ist wohl wahr, nur finde ich es manchmal ganz hilfreich, parallel zum Suchen auch in einem Forum nachzufragen. Man trifft dann während seiner eigenen Suche auf potentielle Lösungen und kriegt dann vielleicht den ausschlaggebenden Tipp nochmal im Forum.