Ein Argument free(void *)
(eingeführt in Unix V7) hat einen weiteren großen Vorteil gegenüber den früheren zwei Argumenten, mfree(void *, size_t)
die ich hier nicht erwähnt habe: Ein Argument free
vereinfacht jede andere API, die mit Heapspeicher arbeitet, dramatisch . Wenn beispielsweise free
die Größe des Speicherblocks benötigt wird, strdup
müssten irgendwie zwei Werte (Zeiger + Größe) anstelle von einem (Zeiger) zurückgegeben werden, und C macht Rückgaben mit mehreren Werten viel umständlicher als Rückgaben mit einem Wert. Stattdessen char *strdup(char *)
müssten wir schreiben char *strdup(char *, size_t *)
oder sonst struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *)
. (Heutzutage sieht diese zweite Option ziemlich verlockend aus, da wir wissen, dass NUL-terminierte Zeichenfolgen der "katastrophalste Designfehler in der Geschichte des Computing" sind., aber das ist im Nachhinein gesprochen. In den 70er Jahren wurde die Fähigkeit von C, Strings einfach zu handhaben, char *
tatsächlich als entscheidender Vorteil gegenüber Wettbewerbern wie Pascal und Algol angesehen .) Außerdem strdup
leidet nicht nur dieses Problem, sondern betrifft jedes system- oder benutzerdefinierte Problem Funktion, die Heapspeicher zuweist.
Die frühen Unix-Designer waren sehr kluge Leute, und es gibt viele Gründe, warum dies free
besser ist. mfree
Ich denke, die Antwort auf die Frage ist, dass sie dies bemerkt und ihr System entsprechend entworfen haben. Ich bezweifle, dass Sie eine direkte Aufzeichnung darüber finden werden, was in ihren Köpfen vor sich ging, als sie diese Entscheidung getroffen haben. Aber wir können uns vorstellen.
Stellen Sie sich vor, Sie schreiben Anwendungen in C, die unter V6 Unix mit zwei Argumenten ausgeführt werden sollen mfree
. Sie haben es bisher in Ordnung gebracht, aber das Verfolgen dieser Zeigergrößen wird immer schwieriger, da Ihre Programme ehrgeiziger werden und immer mehr Heap-zugewiesene Variablen verwenden müssen. Aber dann haben Sie eine brillante Idee: Anstatt diese size_t
ständig zu kopieren , können Sie einfach einige Dienstprogrammfunktionen schreiben, die die Größe direkt im zugewiesenen Speicher speichern:
void *my_alloc(size_t size) {
void *block = malloc(sizeof(size) + size);
*(size_t *)block = size;
return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
block = (size_t *)block - 1;
mfree(block, *(size_t *)block);
}
Und je mehr Code Sie mit diesen neuen Funktionen schreiben, desto beeindruckender erscheinen sie. Sie erleichtern nicht nur das Schreiben Ihres Codes, sondern beschleunigen auch Ihren Code - zwei Dinge, die nicht oft zusammenpassen! Bevor Sie diese s überall herumgereicht haben, wurde der CPU-Overhead für das Kopieren erhöht, und Sie mussten häufiger Register (insbesondere für die zusätzlichen Funktionsargumente) verschütten und Speicher verschwenden (da häufig verschachtelte Funktionsaufrufe auftreten in mehreren Kopien des in verschiedenen Stapelrahmen gespeicherten). In Ihrem neuen System müssen Sie noch den Speicher für die Speicherung dersize_t
size_t
size_t
, aber nur einmal, und es wird nirgendwo kopiert. Dies mag wie eine kleine Effizienz erscheinen, aber denken Sie daran, dass es sich um High-End-Maschinen mit 256 KB RAM handelt.
Das macht dich glücklich! Sie teilen Ihren coolen Trick also mit den bärtigen Männern, die an der nächsten Unix-Version arbeiten, aber es macht sie nicht glücklich, es macht sie traurig. Sie sehen, sie waren gerade dabei, eine Reihe neuer Dienstprogrammfunktionen wie hinzuzufügen strdup
, und sie erkennen, dass Leute, die Ihren coolen Trick verwenden, ihre neuen Funktionen nicht verwenden können, da ihre neuen Funktionen alle den umständlichen Zeiger + Größe verwenden API. Und das macht Sie auch traurig, denn Sie erkennen, dass Sie die gute strdup(char *)
Funktion in jedem Programm, das Sie schreiben, selbst neu schreiben müssen, anstatt die Systemversion verwenden zu können.
Aber warte! Dies ist 1977, und die Abwärtskompatibilität wird erst in 5 Jahren erfunden! Und außerdem, niemand ernst tatsächlich nutzt dieses obskure „Unix“ Ding mit Off-Farbbezeichnung. Die erste Ausgabe von K & R ist jetzt auf dem Weg zum Verlag, aber das ist kein Problem - auf der ersten Seite heißt es: "C bietet keine Operationen, um direkt mit zusammengesetzten Objekten wie Zeichenketten umzugehen ... es gibt keinen Heap ... ". Zu diesem Zeitpunkt in der Geschichte string.h
und malloc
sind Herstellererweiterungen (!). Also, schlägt Bearded Man # 1 vor, wir können sie ändern, wie wir wollen; Warum erklären wir Ihren kniffligen Allokator nicht einfach zum offiziellen Allokator?
Ein paar Tage später sieht Bearded Man # 2 die neue API und sagt: Hey, warte, das ist besser als zuvor, aber es wird immer noch ein ganzes Wort pro Zuordnung ausgegeben, um die Größe zu speichern. Er sieht dies als das Nächste zur Gotteslästerung an. Alle anderen sehen ihn an, als wäre er verrückt, denn was können Sie sonst noch tun? In dieser Nacht bleibt er spät dran und erfindet einen neuen Allokator, der die Größe überhaupt nicht speichert, sondern sie im Handumdrehen durch Bitshifts mit schwarzer Magie auf den Zeigerwert ableitet und sie austauscht, während die neue API an Ort und Stelle bleibt. Die neue API bedeutet, dass niemand den Wechsel bemerkt, aber sie bemerken, dass der Compiler am nächsten Morgen 10% weniger RAM verwendet.
Und jetzt sind alle glücklich: Sie erhalten Ihren einfacher zu schreibenden und schnelleren Code, Bearded Man # 1 kann einen schönen einfachen Code schreiben strdup
, den die Leute tatsächlich verwenden werden, und Bearded Man # 2 - zuversichtlich, dass er sich seinen Unterhalt für ein bisschen verdient hat - - geht zurück zum Herumspielen mit Quines . Es versenden!
Zumindest hätte es so passieren können.