Sprachen
»de« en 
Benutzerkonto
Benutzername

Passwort


Zeichenketten (strings): char * oder std::string?

Es stellt sich die Frage, ob für Zeichenketten std::string aus der C++ Standard Template Library verwendet werden soll, oder doch lieber der traditionelle "char *" Pointer.

Für std::string spricht die einfachere Programmierung (der Programmierer muss sich nicht um die Allokation von Speicher kümmern) und die Immunität gegen Bufferoverruns, was die Sicherheit verbessert.

Aber wie viel langsamer ist die Verwendung von std::string? Und wie viel RAM wird bei beiden Versionen gebraucht?

Geschwindigkeitsvergleich Stringerzeugung und -löschung

Im ersten Beispielprogramm wird 1000 mal dynamisch ein std::string Objekt erzeugt und eine Zeichenkette zugewiesen. Optional kann die Zeichenkette jedes Mal ausgegeben werden. Nach jedem Durchlauf wird die Zeichenkette wieder gelöscht.
#include <iostream>
#include <string>
int main(){
  long int i;
  std::string *text;
  for (i=0;i<1000;i++){
   text = new std::string;
   *text = "The quick brown fox jumped over the lazy dog";
#ifdef OUTPUT
   std::cout << *text << std::endl;
#endif
   delete text;
  }
}

Im zweiten Beispielprogramm wird statt dem std::string ein char * verwendet und mit malloc() Speicher Zugewiesen.

#include <iostream>
#include <malloc.h>
#include <string.h>
int main(){
  long int i;
  char *text;
  for (i=0;i<1000;i++){
   text = (char *) malloc(50);
   strncpy(text,"The quick brown fox jumped over the lazy dog",50);
#ifdef OUTPUT
   std::cout << text << std::endl;
#endif
   free(text);
  }
}
Die beiden Programme werden mit verschiedenen Optionen kompiliert und mit "time" auf ihre Performance getestet. Bei der Version mit Output wird die Ausgabe auf der Kommandozeile nach /dev/null umgeleitet. Der verwendete Compiler ist ein gcc Version 3.3.5, libc6 Version 2.3.2

Resultate auf einem i486, 33MHz, 48MB Speicher:

1000 Zeichenketten erzeugen und löschen std::string char *
ohne Optimierung (-O0), ohne Output
real    0m0.363s
user    0m0.170s
sys     0m0.010s
real    0m0.353s
user    0m0.130s
sys     0m0.040s
mit Optimierung (-O2), ohne Output
real    0m0.367s
user    0m0.140s
sys     0m0.050s
real    0m0.349s
user    0m0.130s
sys     0m0.040s
ohne Optimierung (-O0), mit Output
real    0m3.506s
user    0m1.610s
sys     0m0.040s
real    0m3.296s
user    0m1.540s
sys     0m0.050s
mit Optimierung (-O2), mit Output
real    0m3.336s
user    0m1.580s
sys     0m0.060s
real    0m3.302s
user    0m1.590s
sys     0m0.030s

Es zeigt sich, dass die Erzeugung und Löschung von std::string nur wenige Prozent länger dauert als bei char *, und dass selbst auf einem langsamen Rechner hunderte std::strings pro Sekunde erzeugt und gelöscht werden können.

Schaltet man den Output zu, so wird die Zeit zur Erzeugung und Löschung der Strings beinahe irrelevant.

Es bleibt zu prüfen, wie sich std::string bei komplexeren Stringoperationen wie Vergleichen, Suche im String oder zusammenfügen von zwei Strings verhält.

Geschwindigkeitsvergleich Stringverknüpfung

Version mit std::string
#include <iostream>
#include <string>
int main(){
  long int i;
  std::string *text;
  std::string fulltext;
  for (i=0;i<1000;i++){
   text = new std::string;
   *text = "The quick brown fox jumped over the lazy dog";
   fulltext += *text;
   fulltext += "\n";
   delete text;
}
#ifdef OUTPUT
  std::cout << fulltext << std::endl;
#endif
}
Version mit char *
#include <iostream>
#include <malloc.h>
#include <string.h>

int main(){
  long int i;
  size_t strsize;
  char *text;
  char *fulltext=NULL;
  fulltext = (char *) malloc(1);
  fulltext[0]='\0';

  for (i=0;i<1000;i++){
   text = (char *) malloc(50);
   strcpy(text,"The quick brown fox jumped over the lazy dog");
   strsize = strlen(fulltext);
   strsize += strlen(text);
   strsize += 2; // for the \n and the \0
   fulltext = (char *) realloc(fulltext,strsize);
   strcat(fulltext,text);
   strcat(fulltext,"\n");
   free(text);
}
#ifdef OUTPUT
  std::cout << fulltext << std::endl;
#endif
  free(fulltext);
}
1000 Zeichenketten zusammenfügen std::string char *
ohne Optimierung (-O0), mit Output
real    0m1.601s
user    0m1.530s
sys     0m0.050s
real    0m18.407s
user    0m18.000s
sys     0m0.060s
mit Optimierung (-O2), mit Output
real    0m1.591s
user    0m1.530s
sys     0m0.050s
real    0m18.524s
user    0m18.030s
sys     0m0.060s

Hier zeigt sich, dass die Version mit char* sogar deutlich unterlegen ist. Grund: in std::string ist die Länge des Strings gespeichert. Somit sind keine Aufrufe von strlen() nötig. strlen() hat zur Bestimmung der Stringlänge keine andere Möglichkeit, als vom Anfang des Strings an jedes einzelne Zeichen mit '\0' zu vergleichen bis das Ende gefunden wurde. strcat() muss ebenfalls das Ende des Sourcesrings suchen, bis der Destinationstring angehängt werden kann, ist also ebenfalls langsam.

Wären die Längen der Strings (wie in diesem Beispiel) schon im Voraus bekannt, könnte auf strlen() und strcat() verzichtet werden und die neuen Längen jeweils in separaten Variablen gespeichert und/oder berechnet werden. Allerdings würde der Aufwand dazu gross, und der Code unübersichtlich und somit unter Umständen auch unsicher.

Codegrösse und Speicherverbrauch

Im folgenden Beispiel sollen 1000 Strings angelegt und nicht mehr freigegeben werden. Wie gross wird dabei das ausführbare Programm? Und wieviel Speicher wird zur Laufzeit angeforderd?

Version mit std::string
#include <iostream>
#include <string>
int main(){
  long int i;
  std::string text[1000];
  for (i=0;i<1000;i++){
   text[i] = "The quick brown fox jumped over the lazy dog";
#ifdef OUTPUT
   std::cout << text[i] << std::endl;
#endif
}
  sleep(1000);
}
Version mit char *
#include <iostream>
#include <malloc.h>
#include <string.h>
int main(){
  long int i;
  char *text[1000];
  for (i=0;i<1000;i++){
   text[i] = (char *) malloc(45);
   strncpy(text[i],"The quick brown fox jumped over the lazy dog",44);
#ifdef OUTPUT
   std::cout << text[i] << std::endl;
#endif
}
  sleep(1000);
}
Grössen der gestrippten Binaries ohne Optimierung (g++ -O0)
-rwxr-xr-x  1 stefan stefan 5196 2005-05-15 16:08 string
-rwxr-xr-x  1 stefan stefan 4680 2005-05-15 16:08 charstern
Gr&oumml;ssen der gestrippten Binaries mit Optimierung (g++ -Os)
-rwxr-xr-x  1 stefan stefan 5052 2005-05-15 16:11 string
-rwxr-xr-x  1 stefan stefan 4560 2005-05-15 16:11 charstern

Die char* Version wird etwa 10% kleiner.

Speicherverbrauch laut ksysguard, gestrippt
Programm VmSize VmRss
charstern (g++ -O0) 2'452 868
string (g++ -O0) 2'452 904
charstern (g++ -Os) 2'452 868
string (g++ -Os) 2'454 900
charstern (g++ -O2) 2'452 868
string (g++ -O2) 2'452 900

Fazit

Mit char* statt std::string kann durchaus etwa 10% Leistung gewonnen und etwas Speicher gespart werden. Allerdings ist der Gewinn gering, der Code wird einiges komplizierter und unsicherer. Zudem besteht die Gefahr, bei unsorgfältigem Gebrauch von strlen() und strcpy() sogar sehr massiv Performance zu verlieren.

char * kann nur empfohlen werden, wenn die Längen der Strings von Anfang an bekannt sind und keine komplizierten Operationen mit den Strings durchgeführt werden.

std::string ist sehr bequem und sicher zu gebrauchen und braucht im schlimmsten Fall doch nur etwa 10% mehr Ressourcen. In vielen Fällen ist std::string sogar performanter.


© 2005 by Stefan Heimers
Kürzlich geändert
Profiler (de)
2023-12-21 12:43:54
Solarmodultest (de)
2023-12-21 12:43:54
SVR4004EL (de)
2023-12-21 12:43:54
Compileroptionen (de)
2023-12-21 12:43:54
VCR N1700 (de)
2023-12-21 12:43:54
Reparaturen (de)
2023-12-21 12:43:54