communityWir suchen ständig neue Tutorials und Artikel! Habt ihr selbst schonmal einen Artikel verfasst und seid bereit dieses Wissen mit der Community zu teilen? Oder würdet ihr gerne einmal über ein Thema schreiben das euch besonders auf dem Herzen liegt? Dann habt ihr nun die Gelegenheit eure Arbeit zu veröffentlichen und den Ruhm dafür zu ernten. Schreibt uns einfach eine Nachricht mit dem Betreff „Community Articles“ und helft mit das Angebot an guten Artikeln zu vergrößern. Als Autor werdet ihr für den internen Bereich freigeschaltet und könnt dort eurer literarischen Ader freien Lauf lassen.

CString Management - std::string und Effizienzbetrachtungen Drucken E-Mail
Benutzerbewertung: / 9
SchwachPerfekt 
Geschrieben von: StarShaper   
Dienstag, den 20. September 2005 um 18:30 Uhr
Beitragsseiten
CString Management
CString in char *
VARIANT, STRINGTABLE und temporäre Objekte
std::string und Effizienzbetrachtungen
Alle Seiten

Konvertierungen zwischen std::string und CString

Der Datentyp std::string aus der C++ STL Bibliothek unterscheidet sich grundlegend vom Datentypen CString. CString ist eine MFC Klasse und nur für Windows Programme gedacht die statisch oder dynamisch mit den MFC gelinkt werden. Die std::string Klasse ist Teil der C++ Standard Bibliothek (STL) und daher fester Bestandteil der C++ Programmiersprache. Als solche ist sie plattformunabhängig! Die std::string Klasse ist eine sehr ausgereifte und mächtige Template Klasse und kann problemlos auch in Windows Programmen verwendet werden. Allerdings sollte man sich genau überlegen ob man std::string in seinen MFC Projekten zusammen mit CString verwendet, da es hier unter anderem einige Sachen zu beachten gibt. Außerdem kann es sein das die zur MFC gehörende CString Klasse in der Performance dort ein wenig besser abschneidet als die Standard C++ string Klasse. Der Grund hierfür liegt u.a. darin das CString Funktionsparameteraufrufe mit Call-by-Reference durchführt. Dies erfordert ledeglich einen Pointer, während std::string immer eine volle Kopie durchführt.

Die folgenden Code-Samples zeigen die Konvertierung von std::string in CString.

std::string s("Ein std::string");
CString cs(s.c_str());

und umgekehrt:

CString cs("Ein CString");
std::string s((LPCTSTR)cs);

Nachfolgend noch ein Beispiel dafür wie man einen std::string mit Escape-Sequenzen in mehrere CStrings aufteilt:

CString cs[10];    // 10 CStrings
std::string s = "1\n22\n333";   // STL string
 
int i = 0;
char *token;
char seps[]   = "\n";
CString tmp = s.c_str();
char* buffer = tmp.GetBuffer(tmp.GetLength()+1);
 
token = strtok( buffer, seps );
 
while( token != NULL )
{
    if ( i < 10 )
        cs[i++] = token;
 
    token = strtok( NULL, seps );
}
 
tmp.ReleaseBuffer();
 
for ( int j = 0; j < i; j++ )
    MessageBox(cs[j]);

CString Effizienz

Ein Problem von CString ist das es bestimmte Ineffizienzen vor ihnen versteckt. Andererseits bedeutet das auch, dass es bestimmte Effizienzen implementieren kann. Sie werden vielleicht bei dem folgenden Beispiel dazu verleitet werden zu sagen das er furchbar ineffizient ist.

CString s = SomeCString1;
s += SomeCString2;
s += SomeCString3;
s += ",";
s += SomeCString4;

Zumindest im Vergleich zu diesem:

char s[1024];
lstrcpy(s, SomeString1);
lstrcat(s, SomeString2);
lstrcat(s, SomeString 3);
lstrcat(s, ",");
lstrcat(s, SomeString4);

Nach allem denken sie sich nun vielleicht, das im ersten Beispiel der Code zuerst einen Buffer allokiert um den SomeCString1 aufzunehmen, dann kopiert es diesen string darein, dann merkt es das es eine Verknüpfung macht, allokiert einen neuen Buffer der groß genug ist um den aktuellen string und den SomeCString2 aufzunehmen, kopiert den Inhalt in den Buffer und verknüpft den SomeCString2 anschließend mit diesem. Dann löscht es den ersten Buffer und ersetzt den Pointer durch einen Pointer der auf den neuen Buffer zeigt und wiederholt das Ganze mit jedem anderen string während es dabei aufgrund der vielen Kopien furchtbar ineffizient ist.

Die Wahrheit ist das es in den meisten Fällen wahrscheinlich nie die Quellstrings (die linke Seite von +=) kopiert.

In VC++ 6.0 werden im Release Modus alle CString Buffer in vorgegebenen Portionen (Quanten) allokiert. Diese sind als Vielfache definiert. Nämlich 64, 128, 256 und 512 Bytes. Das bedeutet das solange die strings nicht sehr lang sind, es sich bei der Erzeugung des verknüpften strings um eine optimierte Version der strcat() Operation handelt (solange der Ort vom Stringende bekannt ist, muss nicht danach gesucht werden, so wie strcat() es machen müsste. Es führt einfach nur ein memcpy an der entsprechenden Stelle durch) und um eine Neuberechnung der Stringlänge. So ist es ungefähr genauso effizient wie der umständliche reine C Code, aber viel einfacher zu schreiben, zu verwalten und zu verstehen.

Diejenigen von ihnen die nicht sicher sind was da wirklich passiert sollten einen Blick in den Quellcode von CString werfen. Der entsprechende Code befindet sich in strcore.cpp im vc7/atlmfc/src/mfc Unterverzeichnis ihrer Visual Studio Installation. Suchen sie nach der ConcatInPlace Methode welche von allen += Operatoren aufgerufen wird.

Aha! Dann ist CString also nicht sehr "effizient". Zumindest wenn ich das hier schreibe:

CString cat("Mew!");

Da der Speicher ja in Vielfachen allokiert wird, werden in diesem Beispiel nicht nur die erforderlichen 5 Byte reserviert. Stattdessen beansprucht das System mindestens 64 Byte. Es werden also 59 Byte einfach verschwendet, oder?

Wenn sie sich das nun denken, sollten sie bereit sein das Ganze aus einem völlig anderem Blickwinkel neu zu lernen. Irgendwann in ihrer Karriere hat ihnen jemand mal beigebracht das sie immer so wenig Platz wie möglich reservieren sollten und dass das eine gute Sache ist.

Allerdings ist das so nicht richtig. Es ignoriert einige ernsthafte und wichtige Aspekte in der Realität.

Wenn sie eingebettete Anwendungen für 16K EPROMs programmieren müssen haben sie einen triftigen Grund das so zu machen. In diesem Bereich ist dies unter anderem auch aufgrund der begrenzten Ressourcen notwendig. Aber wenn sie für Windows auf 1000MHz/256MB RAM Maschinen Anwendungen schreiben, verschwenden sie mit solchen Versuchen ihren Code zu optimieren zum einen nur ihre Zeit und zum anderen wird ihre Anwendung auf diese Art im Gegensatz zu einer nicht manuell "optimierten" Version wahrscheinlich sogar noch schlechter abschneiden.

Oft wird die Größe von strings als der erste Ansatzpunkt angesehen, wo man "optimieren" sollte. Es ist gut strings klein zu halten und schlecht wenn diese größer als erforderlich ausfallen. Das ist Nonsens. Der Effekt ist nämlich, dass bei der Speicherallokierung von lauter kleinen strings nach ein paar Stunden der Heap mit lauter kleinen Speicherstücken vollgestopft ist, welche gänzlich nutzlos sind aber den Speicherbedarf ihrer Anwendung in die Höhe treiben, das Paging erhöhen und zudem unter Umständen den Speicherallokator verlangsamen und somit den gesamten RAM Stück für Stück vereinnahmen. Die Speicherfragmentierung, ein zweitrangiger Effekt, hat auch starken Einfluß auf die System Performance. Eventuell gefährdet es sogar die Systemstabilität was einfach nicht zu akzeptieren ist.

Beachten sie das bei der Kompilierung im Debug Modus die Allokierung immer gleich ist. Dies ist bei der Fehlersuche von Bedeutung.

Nehmen sie mal an das ihre Anwendung mehrere Monate laufen soll. Zum Beispiel wenn ich VC++, Word, PowerPoint, FrontPage, Outlook Express, Firefox und ein paar andere Anwendungen starte und nicht mehr schließe. Ich habe PowerPoint Tage lang ohne Probleme benutzt. Auf der anderen Seite hat man ziemliches Pech wenn man das mit Anwendungen wie dem Adobe FrameMaker versucht. Ich war nur selten in der Lage diese Anwendung zu benutzen ohne sie vier bis sechs mal pro Tag zum Absturz zu bringen. Und immer, weil der Speicher aufgebraucht wurde indem die Auslagerungsdatei in astronomische Höhen Anstieg. Zu genaue Allokation ist eines der Tücken welche zur Gefährdung der Zuverlässigkeit und Stabilität ihrer Anwendungen führen wird.

Aufgrunddessen das CStrings aus Vielfachen von 64 Byte Portionen bestehen, wird der Speicherallokator mit Speicherblöcken vollgepumpt, welche dann fast immer sofort von anderen CStrings wiederverwendet werden können. Auf diese Weise nimmt die Fragmentierung ab, die Speicherallokator Performance zu, der RAM Verbrauch bleibt so gering wie möglich und ihre Anwendung läuft ohne Probleme Monate lang durch.

Nebenbemerkung: Vor vielen Jahren an der CMU, haben wir ein interaktives System geschrieben. Einige Studien über den Speicherallokator zeigten, daß dieser eine Tendenz dazu hatte Speicher schlecht zu fragmentieren. Jim Mitchell, der heute bei Sun Microsystems arbeitet, kreierte einen Speicherallokator welcher Laufzeitstatistiken über die Allokierungsgröße, sowie Abweichungen vom Durchschnitt und vom Standard bei allen Allokierungen anfertigte. Wenn ein Block des Speichers in eine Größe kleiner als der Durchschnitt abzüglich einem s als die vorherrschende Allokation aufgeteilt werden sollte, teilte er sie nicht vollkommen und verhinderte damit das der Allokator mit zu kleinen und unbrauchbaren Teilen vollgestopft wurde. Er benutzte Floating Points innerhalb des Allokators. Seine Beobachtung war das die langfristige Speicherung in Instruktionen, ohne die unbrauchbaren kleinen Speicherblöcke zu ignorieren, bei weitem die Zusatzkosten durch ein paar an der Allokation angewandte Floating Point Operationen überstieg. Er lag richtig.

Denken sie niemals über "Optimierungen" auf Basis von auf einzelnen Codezeilen analysierten schnellen und kleinen Ausdrücken. Optimierungen sollten schnell und klein auf dem gesamten Level ihrer Anwendung sein.

Wenn sie der Meinung sind Optimierungen würde man auf dem Niveau einzelner Codezeilen machen, sollten sie noch einmal nachdenken. Optimierungen auf diesem Level sind von geringer Bedeutung. Wenn sie wirklich mehr über das Thema Optimierungen wissen möchten empfehle ich ihnen sich diesbezüglich seperat im Internet zu informieren. Sie werden wahrscheinlich einige Überraschungen erleben.

Beachten sie das der += Operator ein Speziall-Fall ist. Wenn sie stattdessen den unteren Code schreiben würden, würde jede Anwendung des + Operators einen neuen string erzeugen und eine Kopie ausführen (obwohl es eine optimierte Version ist, da die Länge des strings bekannt ist und die Ineffizienzen von strcat() nicht zum Tragen kommen).

CString s = SomeCString1 + SomeCString2 + SomeCString3 + "," + SomeCString4;

Zusammenfassung

Das sind nur einige Techniken in der Verwendung von CString. Ich benutze diese jeden Tag in meiner Programmierung. Der Umgang mit der CString Klasse ist nicht soo kompliziert, aber die MFC offenbart einiges nicht unmittelbar. So müssen sie vieles davon selbst in Erfahrung bringen. Und mit diesem Tutorial konnten sie ihren Erfahrungsschatz bezüglich CStrings hoffentlich ein kleines Stückchen erweitern.

Danksagungen

Zuletzt noch ein paar Danksagungen an Lynn Wallace für die Entdeckung eines Syntaxfehlers in einem der Beispiele, Brian Ross für seine Kommentare zu BSTR Konvertierungen und Robert Quirk für seine Beispiele zu VARIANT-in-BSTR Umwandlungen.

Dieses Tutorial wurde zum Teil von Joseph M. Newcomer verfasst und freundlicherweise zur Verfügung gestellt. Die hier bereitgestellte deutsche Fassung inklusive aller Erweiterungen steht allein im Verantwortungsbereich des neuen Autors!



Zuletzt aktualisiert am Mittwoch, den 22. August 2007 um 17:22 Uhr
 
AUSWAHLMENÜ