Dynamische Speicherverwaltung erlaubt uns z.B., eine etwas großzügigere Art von String zu implementieren. Der folgende Code stellt sozusagen Konstruktor und Destruktor für eine Art Klasse dar:
typedef struct estr_s { int length, cCompatible; char *content; } eStr; eStr *eStr_new(int len) { eStr *eString = malloc(sizeof(eStr)); if (eString) { eString->length = len; if (!(eString->content = malloc((len+1)*sizeof(char)))) { free(eString); return NULL; } memset(eString->content, 0, len+1); eString->cCompatible = 0; } return eString; } void eStr_free(eStr *eString) { free(eString->content); free(eString); }
eStr *eStr_fromString(char *cString) { eStr *eString=eStr_new(strlen(cString)); if (eString) { strcpy(eString->content, cString); eString->cCompatible = 1; } return eString; }
Anmerkungen:
Ein paar Funktionen, die mit diesen eStrs arbeiten:
eStr *eStr_add(eStr *op1, eStr *op2) { eStr *dest=eStr_new(op1->length+op2->length); if (dest) { memcpy(dest->content, op1->content, op1->length); memcpy(dest->content+op1->length, op2->content, op2->length); } return dest; } int eStr_findChar(eStr *eString, char c, int count) { int i; for (i=0; i<eString->length; i++) { if (eString->content[i]==c) { if (!--count) { return i; } } } return -1; }
Anmerkungen:
Um daraus ein Programm zu machen, müssen noch die nötigen Header eingebunden (das sind in dem Fall string.h und stdlib.h sowie stdio.h für das folgende main) und z.B. eine Main-Funktion dieser Art dazugeschrieben werden:
int main(void) { eStr *kuck=eStr_fromString("Kuckuck, "); eStr *ruft=eStr_fromString("rufts aus dem Wald"); eStr *dest1, *dest2; dest1 = eStr_add(kuck, kuck); dest2 = eStr_add(dest1, ruft); eStr_free(dest1); printf("%s %d\n", dest2->content, eStr_findChar(dest2, 'u', 3)); return 0; }
Dieses Main lässt auch schon ähnen, dass wir ohne weiteres nicht den Komfort von Python werden erreichen können. Einerseits muss für die Stringverkettung natürlich extra eine Funktion aufgerufen werden – der +-Operator lässt sich nicht für eStrs umdefinieren (in C++ und übrigens auch in Python geht dieses so genannte operator overloading – vgl. den Exkurs unten). Andererseits, und das wiegt deutlich schwerer, müssen wir uns selbst drum kümmern, Strings, die wir nicht mehr brauchen, freizugeben.
Man kann sich durchaus Systeme überlegen, die nicht mehr benutzte Daten eigenständig freigeben (Garbage Collectors) – aber das ist relativ kompliziert. Eine Möglichkeit wäre, regelmäßig über alle allozierten Blöcke zu laufen und nachzusehen, ob das Programm noch irgendwelche Pointer auf diese Blöcke hält und sie, wenn nicht, freizugeben. Problematisch dabei ist, dass man einen Überlick sowohl über die allozierten Blöcke als auch die Pointer des Programms haben muss, und dass die Garbage Collection eine ganze Weile dauern kann. Dennoch funktionieren die meisten Garbarge Collectors nach diesem Prinzip, so etwa die von üblichen Java- oder Lisp-Systemen.
Python hat traditionell ein etwas anderes Verfahren verwendet, nämlich Refcounting. Die Idee dabei ist, dass man, wenn man sich einen Pointer auf ein Objekt besorgt, eine dem Objekt zugeordnete Variable, nämlich den Reference Count, inkrementiert, und sie wieder dekrementiert, wenn man den Pointer wieder wegnimmt. Wenn der Refcount eines Objekts auf Null sinkt, wird es freigegeben. Problematisch dabei ist vor allem, dass es einige Disziplin braucht, um die Buchhaltung nicht zu vergessen – in Python selbst merkt man davon natürlich nichts, das Python-System und seine Erweiterungen in C werden davon aber sehr wohl belastet.
Ein anderes Problem des Refcounting sind zirkuläre Referenzen. Das ist in folgenem Python-Code zu sehen:
class Circ: def __init__(self): self.myself = self
Wenn man nun c=Circ() sagt, so ist c.myself eine zweite Referenz das so erzeugte Objekt, dessen Refcount jetzt also zwei ist. Wenn wir später c=None sagen, sollte das Objekt eigentlich freigegeben werden, weil wir damit jede Möglichkeit verloren haben, es nochmal zu benutzen (das Programm hält keine Referenz mehr darauf). Leider ist sein Refcount aber danach immer noch 1, es wird also nicht freigegeben, weil es eben eine Referenz auf sich selbst hat.
Solche Fälle treten nicht sehr häufig auf, und wenn, lassen sie sich meist mit weak references entschärfen (Modul weakref) – so man denn merkt, dass etwas nicht stimmt und wo es nicht stimmt.
Weil sich in komplexere Programme dann aber doch gerne mal (vor allem indirekte) zirkuläre Referenzen einschleichen, haben neuere Python-Fassungen auch einen “richtigen” Garbage Collector; wer darüber mehr wissen will, sei auf die Dokumentation zum gc-Modul verwiesen.