std::string und <vector> im Speicher

BAGZZlash

Moderator
Teammitglied
Ich kann nur ne Handvoll C und kein C++. Zuletzt musste ich mich aber dann doch mal mit etwas C++-Code beschäftigen. Dabei sind mir zwei Fragen gekommen, die ähnlich sind:

1.) Ich bin char-Arrays gewöhnt und weiß, wie sie im Speicher aussehen. Ob die Bytes auf dem Stack oder dem Heap alloziert werden, bestimme ich selbst. Wenn auf dem Heap mit malloc(), muss ich hinterher free()n. Wenn ich stattdessen in C++ einen std::string erzeuge, was passiert dann? Wie sieht der String im Speicher aus? Ich habe das hier gefunden, was meine Fragen zum Teil beantwortet, aber: Ist ein std::string bloß ein Pointer auf ein char- oder wchar_t-Array? Was ist mit "Overhead" gemeint, ist ein std::string womöglich sowas wie ein SAFEARRAY unter Windows? Ist ein std::string nullterminiert? Muss ich einen std::string nach der Verwendung zerstören? Falls ja, wie? Und: Ist std::string eigentlich das Mittel der Wahl für Strings, oder ist das irgendwie veraltet? Falls die Antworten von der Platform abhängen: Ich bin mit Windows unterwegs.

2.) Was ist ein <vector> im Speicher? Beispielsweise ein <vector> von std::strings, ist das ein Pointer auf Pointer? Muss ich einen <vector> nach der Verwendung zerstören?

Weiß jemand die Antworten oder eine Quelle, wo ich gezielt nachlesen kann? Irgendwie ist alles, was ich bisher gefunden habe nur auf die Anwendung dieser Konzepte beschränkt, ich will aber hinter die Kulissen schauen.
 
Das ist leider eine SEHR komplexe Frage, in beiden Fällen, und alles was ich jetzt sage ist extrem vereinfacht, ignoriert einen riesigen Haufen wirklich interessanten Kram, kann sich jeder Zeit ändern, und ist beinahe vollständig implementation-defined, so daß das im engeren Sinn nur für g++ und clang++ gilt, und für die eine C++-Standardbibliothek die ich genauer kenne, libc++.

Erstmal zur einfachsten Frage: Ja string ist empfehlenswrt, char* solltest du für strings idealerweise nicht mehr benutzen. Vector ist auch absolut okay, solang du nicht ganz genau weißt, dass du was anderes brauchst.

Nun aber zur Speicherbelegung: das ist komplex, da da viel optimiert worden ist, in den ursprünglichsten Versionen von C++ war es wirklich so, daß bei jeder Operation ein malloc gemacht wurde. Mittlerweile läuft das oftmals anders: strings haben so genannte short string optimization( in eigentlich allen Implementierungen der letzten 10 Jahre). Generell liegen string-Objekte auf dem Heap, dahinter steckt ein new(das GROB sowas ähnliches beinhaltet wie malloc, intern). Das ist aber natürlich nicht besonders trivial und relativ langsam. Short String Optimization bedeutet(zusammen mit einigen anderen Optimierungen für feststehende string konstanten etc.) im wesentlichen, dass das string-objekt selbst einen kleinen Puffer direkt dabei hat, in dem kurze strings einfach direkt abgespeichert werden, und auch einige Operationen auf denen direkt laufen können, ohne extra zusätzlichen Speicher zu allozieren. Es empfiehlt sich nicht, dir std::string einfach als methoden und einen pointer auf ein char* vorzustellen, das füllt den Kopf nur mit falschen Ideen. Wie es tatsächlich aussieht ist sehr komplex.

Bei vector ist das ganze noch komplexer, das kannst du dir erst rechtn icht als ein paar pointer vorstellen, vector ist so stark optimiert, der hat kaum noch eine Ähnlichkeit mit auch nur einer naiv implementierten verketteten Liste Auch hier wird, generell, bei jedem push_back oder so ein new()-passieren, es sei denn du hast vorher die Kapazität des vector erhöht(mit reserve() kannst du auf einen Schlag z.B. genug Platz für 1000 Integer machen, mit shrink_to_fit ihn wieder verkleinern). Vector muss nach dem Standard ziemlich präzise Laufzeit-Constraints erfüllen: Random Access in O(1), Removal vom Ende: O(1), Random Insert/Removal: lineare Laufzeit. Und das gilt auch nicht für alle Arten von vector, viele Standardbibliotheken spezialisieren das Template vector für standarddatentypen( der Standard beinhaltet das mindestens für vector<bool>). Hinzu kommt dass bei der Templateauflösung noch viel magie passiert, und vector wie string so designed sein muss, dass sie mit constexpr zusammen spielen können(seit C++20). Auch hier ist es wieder keine gute Idee, sich das im Sinne von Pointern vorzustellen. Ja ultimativ liegen Pointer darunter, aber hinter all den Templates etc. ist das wohl kaum hilfreich. Wenn du das makroskopische Verhalten eine Autos erklären willst versuchst du ja auch kein Modell zu bauen, das das aus dem Verhalten von Billiarden Elektronen und Quarks herleitet

Dazu kommen noch Eigenschaften wie CopyAssignable und CopyConstructible, die auch bestimmte Operationen effizienter machen können. Das ist leider etwas, so insgesamt, das kaum mal eben erklärbar ist(und erst recht nicht was die Implementierungsdetails angeht). C++ ist eine der komplexesten Programmiersprachen überhaupt( schon weil mehrere einzelne Teile von C++ turing-vollständig sind und nicht einmal die Grammatik von C++ entscheidbar ist, der Standard ist... "ausführlich" und für normale Menschen kaum noch zu überschauen) und C++-Compiler, besonders mit ihren Optimierungen die sie mittlerweile so haben, sind ziemlich sicher die arkanste und komplizierteste Software, die ich jemals gesehen habe, daher wirst du kaum Literatur finden, die das irgendwie erklären wird(und ich glaube auch nicht, dass das dabei, gutes C++ zu schreiben, besonders hilft). Du musst bedenken: die unfassbar gute Reihe "The Art of Computer Programming" hat damit begonnen, dass Knuth die Grundlagen für ein Buch über Compiler niederschreiben wollte. Nach Jahrzehnten ist sie nicht fertig, und sowohl in der Mathematik, als auch in der Informatik und in der Technik(früher hatten PCs keine resourcen für Link-Time-Optimization und dergleichen) geht es so schnell vorwärts, dass es wohl keinen Menschen gibt, der all das noch voll überblickt.

Explizites delete()/delete[] solltest du in C++ i.A. umgehen, und einmal nach RAII suchen, das ist was man tun sollte.

Und auf virtual bin ich noch überhaupt nicht eingegangen, das würde das ganze u.U. noch mehr verkomplizieren.

Ich hoffe das hat dir zumindest etwas geholfen.

MfG,

Lowl3v3l
 
Vielen Dank für Deine ausführliche Antwort, das hat den Horizont wieder ein Stückchen erweitert. Okay, dann werde ich versuchen, mich mal wirklich auf das höhere Abstraktionslevel einzulassen. :)
 
Ist auch besser so ;) Speziell bei den STL-Klassen ist eigentlich alles das du wissen musst: die sind wirklich okay, und vermutlich schneller, wenn richtig angewandt, als alles das >90% der C-Programmierer jemals hin kriegen würden, wenn sie anfingen, selbst irgendwas zu stricken.
 
Jup, aber man sollte sich halt seine Kämpfe aussuchen ;) Auch zu Low-Level gehört zu wissen, was sich zu optimieren lohnt und was nicht. Vor allem, wenn man kein Wizard ist, der das mal eben kann. Und gerade bei der C++-Standardbibliothek können es 9 von 10 Programmierern nicht "mal eben" besser. Ich denk da an std::sort oder so. Der ist so optimiert, dass schon der Algorithmus(idR. hybride Sortieralgorithmen, eigentlich immer Introsort, obwohl ich auch schon von Versuchen gehört hab, wie in Java oder Python Timsort zu benutzen, im Fall einer Parallel-STL(wie bei llvm die pstl) sogar noch nebenläufige Sortieralgorithmen) an sich einen nicht-kleinen Teil der C(++)-Programmierer überfordern wird. Daher rate ich idR davon ab, sowas selbst zu basteln.

P.S. und selbst wenn man es optimiert auf seinen einen Fall selbst strickt ist es selten, dass man daraus einen signifikanten Speedup hat, der sich tatsächlich lohnt, und es wäre besser, Algorithmus und Programmdesign zu verbessern. Genau so wie bei Inline-Assembler, lohnt sich nur in sehr spezifischen Fällen, mit denen kaum ein Programmierer je zu tun haben wird. Was natürlich ein absolut valider Grund ist ist, es mal selbst zu machen, um es zu verstehen. Aber sowas gehört dann nicht in "Produktiv"-Code.
 
Zuletzt bearbeitet:
Da hast Du sicher recht. Naja, ich will da gar nichts optimieren oder selbst basteln, eigentlich will ich damit so wenig wie möglich zu tun haben. Aber wenn ich's muss, dann will ich halt auch wissen, womit ich's zu tun habe...
 
Wie gesagt, dazu müsstest du dir konkret die Compilerimplementierung die du nutzen willst ansehen und wie die das macht, aber ich erwarte da keinerlei Mehrwert für dich, selbst wenn du dahinter steigst(was mich überraschen würde, ich kenne nicht einmal eine Hand voll Programmierer, auch langjährige Veteranen, die sowas in vertretbarer Zeit verstehen würden).
 
Alles klar. Wie gesagt, ich kann damit leben. Hätte ja auch sein können, dass die Antwort einfach ist. Sowas wie: "Struct mit eingebettetem Pointer auf Array und Metainformationen und dem ganzen Code dahinter, der malloc usw. macht. Fertig." Aber wenn dem nicht so ist und die Antwort lautet: "Ist megakompliziert", ist mir das auch recht. :cool:(y) Wird ja auch seinen Grund haben, dass das nicht einfach irgendwo steht. Hatte mich ja schon etwas umgeschaut.
 
Zurück
Oben Unten