Warum nimmt `free` in C nicht die Anzahl der freizugebenden Bytes an?


84

Um es klar zu machen: Ich weiß das mallocund bin freein der C-Bibliothek implementiert, die normalerweise Speicherblöcke vom Betriebssystem zuweist und eine eigene Verwaltung durchführt, um kleinere Speichermengen an die Anwendung zu verteilen und die Anzahl der zugewiesenen Bytes zu verfolgen . Diese Frage lautet nicht: Woher weiß, wie viel frei ist ?

Ich möchte vielmehr wissen, warum freedies überhaupt so gemacht wurde. Da ich eine einfache Sprache bin, halte ich es für absolut vernünftig, einen C-Programmierer zu bitten, nicht nur zu verfolgen, welcher Speicher zugewiesen wurde, sondern auch, wie viel (tatsächlich stelle ich häufig fest, dass ich am Ende die Anzahl der Bytes verfolge malloced sowieso). Mir fällt auch ein, dass die explizite Angabe der Anzahl der Bytes, die freemöglicherweise einige Leistungsoptimierungen ermöglichen, z. B. ein Allokator mit separaten Pools für unterschiedliche Zuordnungsgrößen, anhand der Eingabeargumente bestimmen kann, von welchem ​​Pool befreit werden soll Insgesamt wäre weniger Platz erforderlich.

Kurz gesagt, warum wurden mallocund wurden sie so freeerstellt, dass sie intern die Anzahl der zugewiesenen Bytes verfolgen müssen? Ist es nur ein historischer Unfall?

Eine kleine Änderung: Einige Leute haben Punkte wie "Was ist, wenn Sie einen anderen Betrag als den von Ihnen zugewiesenen freigeben" angegeben? Meine imaginäre API könnte einfach eine erfordern, um genau die Anzahl der zugewiesenen Bytes freizugeben. Mehr oder weniger zu befreien könnte einfach UB oder Implementierung definiert sein. Ich möchte die Diskussion über andere Möglichkeiten jedoch nicht entmutigen.


18
Weil es bereits mühsam ist, die Zuordnungen selbst zu verfolgen, und es würde den Code sogar noch komplizierter machen, wenn Sie zusätzlich die Größe verfolgen müssten.
Jens Gustedt

11
Ich kann mir mehrere Gründe vorstellen: Warum den Benutzer dazu bringen, es zu tun, wenn er es nicht muss? Was ist, wenn der Benutzer es vermasselt? Es ist sowieso eine Art überflüssige Frage. Wenn sie die andere Wahl getroffen hätten, würden Sie immer noch fragen, warum.
BoBTFish

15
@BoBTFish: Dies ist C, von dem wir sprechen, nicht Python oder sogar C ++. Der Benutzer muss bereits eine $ h! 1 Tonne machen, die er nicht muss. Das ist kein Grund.
user541686

4
Auch dazu hat K & R nichts zu sagen. Wir können alles spekulieren, was wir wollen, aber ich denke, der ursprüngliche Grund könnte in der Geschichte verloren gehen .
BoBTFish

35
Sie können nicht verlangen, dass der Programmierer die Größe des Blocks korrekt übergibt, da der Aufrufer von mallocdie Größe des zurückgegebenen Blocks nicht kennt . mallocGibt häufig einen Block zurück, der größer als angefordert ist. Bestenfalls könnte der Programmierer die im malloc()Aufruf angeforderte Größe übergeben , was dem Implementierer überhaupt nicht helfen würde free().
Ben Voigt

Antworten:


97

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 freevereinfacht jede andere API, die mit Heapspeicher arbeitet, dramatisch . Wenn beispielsweise freedie Größe des Speicherblocks benötigt wird, strdupmü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 strdupleidet 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 freebesser ist. mfreeIch 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_tstä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_tsize_tsize_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.hund mallocsind 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.


14
Ähm, nur für den Fall, dass es unklar ist, ist dies ein Flug der Phantasie, mit bestätigenden Details, die künstlerische Vielseitigkeit bieten. Jede Ähnlichkeit mit lebenden oder toten Personen ist nur deshalb so, weil alle Beteiligten irgendwie gleich aussahen . Bitte nicht mit der tatsächlichen Geschichte verwechseln.
Nathaniel J. Smith

5
Du gewinnst. Dies scheint mir die plausibelste Erklärung (und die beste Schrift) zu sein. Auch wenn sich hier alles als falsch oder ungültig herausstellt, ist dies die beste Antwort für die hervorragenden Darstellungen bärtiger Männer.
Jaymmer - Wiedereinsetzung Monica

Wow, eine Antwort auf dieser Seite, die tatsächlich plausibel klingt. +1 von mir.
user541686

Noch besser - Eine Antwort auf dieser Seite, die wirklich Spaß macht! +1 auch.
David C. Rankin

Ich frage mich, ob irgendwelche bemerkenswerten Pascal-Systeme einen durch Müll gesammelten String-Pool auf ähnliche Weise wie BASIC-Interpreter von Mikrocomputern verwendet haben. Die Semantik von C würde mit so etwas nicht funktionieren, aber in Pascal könnte so etwas ganz gut gehandhabt werden, wenn der Code nachvollziehbare Stapelrahmen beibehalten würde (was viele Compiler sowieso taten).
Supercat

31

"Warum nimmt freein C nicht die Anzahl der freizugebenden Bytes?"

Weil es nicht nötig ist und es sowieso keinen Sinn ergibt.

Wenn Sie etwas zuweisen, möchten Sie dem System mitteilen, wie viele Bytes zugewiesen werden sollen (aus offensichtlichen Gründen).

Wenn Sie Ihr Objekt jedoch bereits zugewiesen haben, wird jetzt die Größe des Speicherbereichs bestimmt, den Sie zurückerhalten. Es ist implizit. Es ist ein zusammenhängender Speicherblock. Sie können einen Teil davon nicht freigeben (vergessen wir realloc(), das ist sowieso nicht das, was es tut), Sie können nur die gesamte Sache freigeben . Sie können X-Bytes auch nicht "freigeben" - Sie geben entweder den Speicherblock frei, von dem Sie ihn erhalten haben, malloc()oder Sie tun dies nicht.

Und jetzt, wenn Sie es freigeben möchten, können Sie dem Speichermanagersystem einfach sagen: "Hier ist dieser Zeiger, free()der Block , auf den er zeigt." - und der Speichermanager weiß, wie das geht, entweder weil er die Größe implizit kennt oder weil er möglicherweise nicht einmal die Größe benötigt.

Beispielsweise malloc()pflegen die meisten typischen Implementierungen eine verknüpfte Liste von Zeigern auf freie und zugewiesene Speicherblöcke. Wenn Sie einen Zeiger an übergeben free(), wird nur nach diesem Zeiger in der Liste "zugewiesen" gesucht, die Verknüpfung des entsprechenden Knotens aufgehoben und an die Liste "frei" angehängt. Es brauchte nicht einmal die Größe der Region. Diese Informationen werden nur benötigt, wenn möglicherweise versucht wird, den betreffenden Block wiederzuverwenden.


Wenn ich 100 Dollar von Ihnen leihe und dann wieder 100 Dollar von Ihnen leihe und das dann noch fünf Mal mache, ist es Ihnen wirklich wichtig, dass ich sieben Mal Geld von Ihnen geliehen habe (es sei denn, Sie berechnen tatsächlich Zinsen!)? Oder interessiert es Sie nur, dass ich 700 Dollar von Ihnen geliehen habe? Das Gleiche gilt hier: Das System kümmert sich nur um nicht zugewiesenen Speicher, es kümmert sich nicht (und muss und sollte nicht) darum, wie der zugewiesene Speicher aufgeteilt wird.
user541686

1
@Mehrdad: Nein, und das tut es nicht. C tut es jedoch. Sein gesamter Zweck ist es, die Dinge (ein wenig) sicherer zu machen. Ich weiß wirklich nicht, wonach Sie hier suchen.
Leichtigkeitsrennen im Orbit

12
@ user3477950: Es muss nicht die Anzahl der Bytes übergeben werden : Ja, weil es so entworfen wurde. Das OP fragte, warum es so entworfen wurde.
Deduplikator

5
"Weil es nicht muss" - es hätte genauso gut so gestaltet sein können, dass es muss.
user253751

5
@Mehrdad, dass eine völlig unklare und fehlerhafte Analogie. Wenn Sie 4 Byte hundert Mal zuweisen, spielt es definitiv eine Rolle, welches Sie frei haben. Das Befreien des ersten ist nicht dasselbe wie das Befreien des zweiten. mit dem Geld auf der anderen Seite spielt es keine Rolle, ob Sie die erste oder die zweite Ausleihe zuerst zurückzahlen, es ist nur ein großer Haufen
user2520938

14

C ist möglicherweise nicht so "abstrakt" wie C ++, aber es soll immer noch eine Abstraktion über Assembly sein. Zu diesem Zweck werden die Details der untersten Ebene aus der Gleichung herausgenommen. Dies verhindert, dass Sie sich größtenteils mit Ausrichtung und Polsterung herumschlagen müssen, was dazu führen würde, dass alle Ihre C-Programme nicht portierbar sind.

Kurz gesagt, dies ist der gesamte Punkt beim Schreiben einer Abstraktion .


9
Ich bin mir nicht sicher, was Ausrichtung oder Polsterung damit zu tun haben. Die Antwort beantwortet eigentlich nichts.
user541686

1
@Mehrdad C ist keine x86-Sprache, es versucht (mehr oder weniger) portabel zu sein und befreit den Programmierer somit von dieser erheblichen Belastung. Sie können diese Ebene ohnehin auf verschiedene andere Arten erreichen (z. B. Inline-Assembly), aber Abstraktion ist der Schlüssel. Ich stimme dieser Antwort zu.
Marco A.

4
@Mehrdad: Wenn Sie nach mallocN Bytes gefragt haben und stattdessen ein Zeiger auf den Anfang einer ganzen Seite zurückgegeben wird (aufgrund von Ausrichtung, Auffüllen oder anderen Einschränkungen) , gibt es für den Benutzer keine Möglichkeit, den Überblick zu behalten - erzwingt dies Es wäre kontraproduktiv.
Michael Foukarakis

3
@MichaelFoukarakis: mallockann einfach immer einen ausgerichteten Zeiger zurückgeben, ohne jemals die Größe der Zuordnung zu speichern. freekönnte dann auf die entsprechende Ausrichtung aufrunden, um sicherzustellen, dass alles ordnungsgemäß freigegeben wird. Ich sehe nicht, wo das Problem liegt.
user541686

6
@Mehrdad: Es gibt keinen Vorteil für all die zusätzliche Arbeit, die Sie gerade erwähnt haben. Durch Übergeben eines sizeParameters an freewird außerdem eine weitere Fehlerquelle geöffnet.
Michael Foukarakis

14

Tatsächlich hat sich im alten Unix-Kernel-Speicher-Allokator mfree()ein sizeArgument ergeben. malloc()und mfree()behielt zwei Arrays (eines für den Kernspeicher, eines für den Swap), die Informationen zu freien Blockadressen und -größen enthielten.

Bis Unix V6 gab es keinen Userspace-Allokator (Programme würden nur verwenden sbrk()). In Unix V6 enthielt iolib einen Allokator mit alloc(size)und einen free()Aufruf, für den kein Größenargument erforderlich war . Vor jedem Speicherblock standen seine Größe und ein Zeiger auf den nächsten Block. Der Zeiger wurde nur für freie Blöcke verwendet, wenn die freie Liste durchlaufen wurde, und wurde als Blockspeicher für verwendete Blöcke wiederverwendet.

In Unix - 32V und in Unix V7 wurde dies durch eine neue ersetzt malloc()und free()Implementierung, wo free()kein nehmen hat sizeArgument. Die Implementierung war eine zirkuläre Liste. Vor jedem Block befand sich ein Wort, das einen Zeiger auf den nächsten Block und ein "besetztes" (zugewiesenes) Bit enthielt. Also habe malloc()/free()ich nicht einmal eine explizite Größe im Auge behalten.


10

Warum nimmt freein C nicht die Anzahl der freizugebenden Bytes?

Weil es nicht muss. Die Informationen sind bereits in der internen Verwaltung von malloc / free verfügbar.

Hier sind zwei Überlegungen (die möglicherweise zu dieser Entscheidung beigetragen haben oder nicht):

  • Warum sollte eine Funktion einen Parameter erhalten, den sie nicht benötigt?

    (Dies würde praktisch den gesamten Client-Code, der auf dynamischem Speicher basiert, komplizieren und Ihrer Anwendung völlig unnötige Redundanz hinzufügen.) Das Verfolgen der Zeigerzuordnung ist bereits ein schwieriges Problem. Das Verfolgen der Speicherzuweisungen zusammen mit den zugehörigen Größen würde die Komplexität des Client-Codes unnötig erhöhen.

  • Was würde die veränderte freeFunktion in diesen Fällen bewirken?

    void * p = malloc(20);
    free(p, 25); // (1) wrong size provided by client code
    free(NULL, 10); // (2) generic argument mismatch

    Würde es nicht frei sein (einen Speicherverlust verursachen?)? Den zweiten Parameter ignorieren? Stoppen Sie die Anwendung, indem Sie exit aufrufen? Die Implementierung würde zusätzliche Fehlerpunkte in Ihrer Anwendung hinzufügen, für eine Funktion, die Sie wahrscheinlich nicht benötigen (und wenn Sie sie benötigen, lesen Sie meinen letzten Punkt unten - "Implementierung der Lösung auf Anwendungsebene").

Vielmehr möchte ich wissen, warum Free überhaupt so gemacht wurde.

Weil dies der "richtige" Weg ist, dies zu tun. Eine API sollte die Argumente erfordern, die sie zur Ausführung ihrer Operation benötigt, und nicht mehr .

Mir fällt auch ein, dass die explizite Angabe der Anzahl der freizugebenden Bytes einige Leistungsoptimierungen ermöglichen kann, z. B. kann ein Allokator mit separaten Pools für unterschiedliche Zuordnungsgrößen anhand der Eingabeargumente bestimmen, von welchem ​​Pool befreit werden soll. und es würde insgesamt weniger Platz über dem Kopf sein.

Die richtigen Möglichkeiten, dies umzusetzen, sind:

  • (auf Systemebene) innerhalb der Implementierung von malloc - nichts hindert den Bibliotheksimplementierer daran, malloc zu schreiben, um intern verschiedene Strategien basierend auf der empfangenen Größe zu verwenden.

  • (auf Anwendungsebene), indem Sie malloc und free in Ihre eigenen APIs einbinden und diese stattdessen verwenden (überall in Ihrer Anwendung, wo Sie sie möglicherweise benötigen).


6
@ user3477950: Es muss nicht die Anzahl der Bytes übergeben werden : Ja, weil es so entworfen wurde. Das OP fragte, warum es so entworfen wurde.
Deduplikator

4
"Weil es nicht muss" - es hätte genauso gut so gestaltet sein können, dass es muss, indem diese Informationen nicht gespeichert werden.
user253751

1
In Bezug auf Punkt 2 frage ich mich, ob freies (NULL) definiertes Verhalten ist. Aha, "Alle standardkonformen Versionen der C-Bibliothek behandeln kostenlos (NULL) als No-Op" - Quelle stackoverflow.com/questions/1938735/…
Mawg sagt, Monica

10

Fünf Gründe fallen mir ein:

  1. Es ist bequem. Es entfernt eine ganze Menge Overhead vom Programmierer und vermeidet eine Klasse von extrem schwer zu verfolgenden Fehlern.

  2. Es eröffnet die Möglichkeit, einen Teil eines Blocks freizugeben. Da Speichermanager normalerweise Tracking-Informationen benötigen, ist nicht klar, was dies bedeuten würde.

  3. Leichtigkeitsrennen im Orbit sind genau das Richtige für Polsterung und Ausrichtung. Aufgrund der Art der Speicherverwaltung unterscheidet sich die tatsächlich zugewiesene Größe möglicherweise von der von Ihnen gewünschten Größe. Dies bedeutet, dass sowohl freeeine Größe als auch ein Standort erforderlich sein solltenmalloc geändert werden müssten, um auch die tatsächlich zugewiesene Größe zurückzugeben.

  4. Es ist ohnehin nicht klar, dass das Übergeben der Größe tatsächlich einen Vorteil hat. Ein typischer Speichermanager verfügt über 4 bis 16 Byte Header für jeden Speicherblock, einschließlich der Größe. Dieser Chunk-Header kann für zugewiesenen und nicht zugewiesenen Speicher verwendet werden. Wenn benachbarte Chunks frei werden, können sie zusammengeklappt werden. Wenn Sie den Anrufer dazu bringen, den freien Speicher zu speichern, können Sie wahrscheinlich 4 Bytes pro Block freigeben, indem Sie kein separates Größenfeld im zugewiesenen Speicher haben, aber dieses Größenfeld wird wahrscheinlich sowieso nicht gewonnen, da der Anrufer es irgendwo speichern muss. Aber jetzt sind diese Informationen im Speicher verstreut, anstatt sich vorhersehbar im Header-Block zu befinden, was wahrscheinlich sowieso weniger betrieblich effizient ist.

  5. Auch wenn es ist effizienter ist es radikal unwahrscheinlich Ihr Programm verbringt viel Zeit zu befreien Speicher sowieso so würde der Nutzen klein sein.

Im Übrigen lässt sich Ihre Vorstellung von separaten Zuweisern für Elemente unterschiedlicher Größe ohne diese Informationen leicht umsetzen (anhand der Adresse können Sie bestimmen, wo die Zuordnung erfolgt ist). Dies erfolgt routinemäßig in C ++.

Später hinzugefügt

Eine andere, ziemlich lächerliche Antwort hat std :: allocator als Beweis dafür angeführt , dass freedies so funktionieren könnte, aber tatsächlich ist es ein gutes Beispiel dafür, warum freedies nicht so funktioniert. Es gibt zwei wesentliche Unterschiede zwischen dem, was malloc/ freedo und dem, was std :: allocator tut. Erstens mallocund freemit Blick auf den Benutzer - sie sind für die allgemeinen Programmierer konzipiert, mit denen sie arbeiten können - währendstd::allocator ausgelegt ist Spezialist Speicherzuordnung zu der Standardbibliothek zur Verfügung zu stellen. Dies ist ein schönes Beispiel dafür, wann der erste meiner Punkte keine Rolle spielt oder nicht. Da es sich um eine Bibliothek handelt, sind die Schwierigkeiten beim Umgang mit der Komplexität der Tracking-Größe dem Benutzer ohnehin verborgen.

Zweitens arbeitet std :: allocator immer mit Elementen gleicher Größe. Dies bedeutet, dass es möglich ist, die ursprünglich übergebene Anzahl von Elementen zu verwenden, um zu bestimmen, wie viel frei ist. Warum dies von sich freeselbst abweicht , ist illustrativ. In std::allocatorden Elementen zugeordnet sind immer gleich, bekannt, Größe und immer die gleiche Art von Element sein , so dass sie immer die gleiche Art von Ausrichtungsanforderungen haben. Dies bedeutet, dass der Allokator darauf spezialisiert sein könnte, einfach zu Beginn ein Array dieser Elemente zuzuweisen und sie nach Bedarf zu verteilen. Sie können dies nicht mit tun,free da es keine Möglichkeit gibt, zu garantieren, dass die beste zurückzugebende Größe die angeforderte Größe ist. Stattdessen ist es viel effizienter, manchmal größere Blöcke zurückzugeben, als der Anrufer von * verlangt, und somit auch nichtDer Benutzer oder der Manager muss die genaue Größe verfolgen, die tatsächlich gewährt wurde. Die Weitergabe dieser Art von Implementierungsdetails an den Benutzer ist ein unnötiger Kopfschmerz, der dem Anrufer keinen Nutzen bringt.

- * Wenn jemand diesen Punkt immer noch schwer versteht, bedenken Sie Folgendes: Ein typischer Speicherzuweiser fügt dem Anfang eines Speicherblocks eine kleine Menge von Verfolgungsinformationen hinzu und gibt dann einen Zeigerversatz von diesem zurück. Hier gespeicherte Informationen enthalten beispielsweise normalerweise Zeiger auf den nächsten freien Block. Nehmen wir an, der Header ist nur 4 Byte lang (was tatsächlich kleiner als die meisten realen Bibliotheken ist) und enthält nicht die Größe. Stellen Sie sich dann vor, wir haben einen 20-Byte-freien Block, wenn der Benutzer nach einem 16-Byte-Block fragt, einem naiven Das System würde den 16-Byte-Block zurückgeben, dann aber ein 4-Byte-Fragment hinterlassen, das niemals verwendet werden könnte, um jedes Mal Zeit zu verschwendenmallocwird gerufen. Wenn der Manager stattdessen einfach den 20-Byte-Block zurückgibt, werden diese unordentlichen Fragmente vor dem Aufbau bewahrt und der verfügbare Speicher kann sauberer zugeordnet werden. Wenn das System dies jedoch korrekt tun soll, ohne die Größe selbst zu verfolgen, muss der Benutzer für jede einzelne Zuordnung die tatsächlich zugewiesene Speichermenge verfolgen, wenn er diese kostenlos zurückgeben soll. Das gleiche Argument gilt für das Auffüllen von Typen / Zuordnungen, die nicht den gewünschten Grenzen entsprechen. Das Erfordernis freeeiner Größe ist daher höchstens entweder (a) völlig nutzlos, da sich der Speicherzuweiser nicht auf die übergebene Größe verlassen kann, um mit der tatsächlich zugewiesenen Größe übereinzustimmen, oder (b) erfordert sinnlos, dass der Benutzer die tatsächliche Verfolgung der Arbeit ausführt Größe, die von jedem vernünftigen Speichermanager leicht gehandhabt werden kann.


# 1 ist wahr. # 2: Ich bin mir nicht sicher, was du meinst. Bei # 3 ist die Ausrichtung nicht so sicher. Für die Ausrichtung müssen keine zusätzlichen Informationen gespeichert werden. # 4 ist Zirkelschluss; Speichermanager benötigen diesen Overhead nur pro Block, da sie die Größe speichern. Sie können ihn daher nicht als Argument dafür verwenden, warum sie die Größe speichern. Und # 5 ist sehr umstritten.
user541686

Ich habe akzeptiert und dann nicht akzeptiert - das scheint eine gute Antwort zu sein, aber die Frage scheint viel Aktivität zu bekommen, ich glaube, ich war etwas verfrüht. Dies gibt mir aber definitiv etwas zum Nachdenken.
Jaymmer - Wiedereinsetzung Monica

2
@ Jaymmer: Ja, es ist verfrüht. Ich schlage vor, mehr als ein oder zwei Tage zu warten, bevor ich akzeptiere, und ich schlage vor, selbst darüber nachzudenken. Es ist eine wirklich interessante Frage, und die meisten / alle Antworten, die Sie zunächst auf eine solche "Warum" -Frage in StackOverflow erhalten, sind nur halbautomatische Versuche, das aktuelle System zu rechtfertigen, anstatt die zugrunde liegende Frage tatsächlich zu beantworten.
user541686

@Mehrdad: Du hast falsch verstanden, was ich in # 4 sage. Ich sage nicht, dass es zusätzliche Größe braucht, ich sage, (a) es wird sich bewegen, wer die Größe speichern muss, damit es keinen Platz spart, und (b) die resultierende Änderung wird es wahrscheinlich tatsächlich schaffen weniger effizient nicht mehr. Was # 5 betrifft, bin ich nicht davon überzeugt, dass es überhaupt umstritten ist: Wir sprechen höchstens davon, ein paar Anweisungen aus dem kostenlosen Anruf zu speichern. Im Vergleich zu den Kosten des kostenlosen Anrufs wird das winzig sein.
Jack Aidley

1
@Mehrdad: Oh, und auf # 3, nein, es werden keine zusätzlichen Informationen benötigt, es wird zusätzlicher Speicher benötigt. Ein typischer Speichermanager, der für die 16-Byte-Ausrichtung ausgelegt ist, gibt einen Zeiger auf einen 128-Byte-Block zurück, wenn er nach einem 115-Byte-Block gefragt wird. Wenn der freeAnruf die freizugebende Größe korrekt übergeben soll, muss er dies wissen.
Jack Aidley

5

Ich poste dies nur als Antwort, nicht weil es die ist, auf die Sie hoffen, sondern weil ich glaube, dass es die einzig plausibel richtige ist:

Es wurde wahrscheinlich ursprünglich als zweckmäßig erachtet und konnte danach nicht verbessert werden.
Es gibt wahrscheinlich keinen überzeugenden Grund dafür. (Aber ich werde dies gerne löschen, wenn gezeigt wird, dass es falsch ist.)

Es würde Vorteile, wenn es möglich war: Sie ein einzelnes großes Stück Speicher zuweisen konnte , dessen Größe Sie vorher kannte, dann zu einem Zeitpunkt ein wenig frei - im Gegensatz zu immer wieder zugeordnet und freigegeben kleine Stücke der Erinnerung. Derzeit sind solche Aufgaben nicht möglich.


Für die vielen (viele 1 !) Von euch, die denken, dass es so lächerlich ist, die Größe zu überschreiten:

Darf ich Sie auf die Entwurfsentscheidung von C ++ für die std::allocator<T>::deallocateMethode verweisen ?

void deallocate(pointer p, size_type n);

Alle Gegenstände in dem Bereich, auf den von gezeigt wird, müssen vor diesem Anruf zerstört werden. muss mit dem Wert übereinstimmen, an den übergeben wird , um diesen Speicher zu erhalten.n Tp
nallocate

Ich denke, Sie werden eine ziemlich "interessante" Zeit haben, um diese Entwurfsentscheidung zu analysieren.


Was operator delete, es stellt sich heraus , dass der 2013 N3778 Vorschlag ( „C ++ Sized Ausplanung“) das beheben soll, auch.


1 Schauen Sie sich einfach die Kommentare unter der ursprünglichen Frage an, um zu sehen, wie viele Personen voreilige Aussagen wie "Die angeforderte Größe ist für den freeAufruf völlig nutzlos " gemacht haben , um das Fehlen des sizeParameters zu rechtfertigen .


5
Für die malloc()Implementierung würde es auch die Notwendigkeit beseitigen, sich irgendetwas über eine zugewiesene Region zu merken , wodurch der Zuordnungsaufwand auf den Ausrichtungsaufwand reduziert würde. malloc()wäre in der Lage, alle Buchhaltung innerhalb der befreiten Stücke selbst zu erledigen. Es gibt Anwendungsfälle, in denen dies eine große Verbesserung wäre. Die Freigabe eines großen Speicherblocks in mehrere kleine Blöcke müsste jedoch nicht empfohlen werden, da dies die Fragmentierung drastisch erhöhen würde.
cmaster - wieder herstellen Monica

1
@cmaster: Diese Anwendungsfälle waren genau die Art, auf die ich mich bezog. Du hast den Nagel auf den Kopf getroffen, danke. In Bezug auf die Fragmentierung: Ich bin mir nicht sicher, wie dies die Fragmentierung im Vergleich zur Alternative erhöht, bei der Speicher in kleinen Blöcken sowohl zugewiesen als auch freigegeben wird.
user541686

std::allocatorzuzuordnet nur Elemente einer bestimmten, bekannte Größe. Es ist kein Allzweck-Allokator, der Vergleich ist Äpfel mit Orangen.
Jack Aidley

Es scheint mir, dass eine teils philosophische, teils gestalterische Entscheidung getroffen wurde, die Standardbibliothek in C zu einem minimalen Satz von Grundelementen zu machen, aus denen praktisch alles aufgebaut werden kann - ursprünglich als Sprache auf Systemebene gedacht und auf Systeme portierbar, die dieser primitive Ansatz macht Sinn. Mit C ++ wurde eine andere Entscheidung getroffen, um die Standardbibliothek sehr umfangreich zu machen (und mit C ++ 11 größer zu werden). Schnellere Entwicklungshardware, größere Speicher, komplexere Architekturen und die Notwendigkeit, die Anwendungsentwicklung auf höherer Ebene anzugehen, tragen möglicherweise zu dieser Änderung des Schwerpunkts bei.
Clifford

1
@ Clifford: Genau - deshalb habe ich gesagt, dass es keinen überzeugenden Grund dafür gibt. Es ist nur eine Entscheidung, die getroffen wurde, und es gibt keinen Grund zu der Annahme, dass sie strikt besser ist als die Alternativen.
user541686

2

malloc und free gehen Hand in Hand, wobei jedem "malloc" ein "free" entspricht. Daher ist es absolut sinnvoll, dass das "freie" Matching mit einem vorherigen "Malloc" einfach die von diesem Malloc zugewiesene Speichermenge freigibt - dies ist der Hauptanwendungsfall, der in 99% der Fälle sinnvoll wäre. Stellen Sie sich alle Speicherfehler vor, wenn der Programmierer bei allen Verwendungen von malloc / free durch alle Programmierer auf der ganzen Welt den in malloc zugewiesenen Betrag verfolgen und dann daran denken muss, denselben freizugeben. Das Szenario, über das Sie sprechen, sollte wirklich die Verwendung mehrerer Mallocs / Frees in einer Art Speicherverwaltungsimplementierung sein.


5
Ich denke , die "alle Stellen Sie sich die [...] Fehler" strittig ist , wenn Sie von anderen Bug Fabriken denken wie gets, printf, manuelle Schleifen (Off-by-one), undefinierten Verhalten, Format-Strings, implizite Konvertierungen, Bit Tricks, et cetera.
Sebastian Mach

1

Ich würde vorschlagen, dass dies daran liegt, dass es sehr praktisch ist, Größeninformationen nicht manuell auf diese Weise zu verfolgen (in einigen Fällen) und auch weniger anfällig für Programmiererfehler.

Außerdem würde realloc diese Buchhaltungsinformationen benötigen, die meiner Meinung nach mehr als nur die Zuordnungsgröße enthalten. dh es ermöglicht die Definition des Mechanismus, nach dem es arbeitet.

Sie könnten Ihren eigenen Allokator schreiben, der in der von Ihnen vorgeschlagenen Weise funktioniert hat, und er wird in C ++ für Pool-Allokatoren häufig auf ähnliche Weise für bestimmte Fälle (mit potenziell massiven Leistungssteigerungen) ausgeführt, obwohl dies im Allgemeinen in Bezug auf den Operator implementiert ist neu für die Zuordnung von Poolblöcken.


1

Ich sehe nicht, wie ein Allokator funktionieren würde, der die Größe seiner Allokationen nicht verfolgt. Wenn dies nicht der Fall wäre, wie würde es wissen, welcher Speicher verfügbar ist, um eine zukünftige mallocAnforderung zu erfüllen ? Es muss mindestens eine Art Datenstruktur gespeichert werden, die Adressen und Längen enthält, um anzuzeigen, wo sich die verfügbaren Speicherblöcke befinden. (Das Speichern einer Liste freier Speicherplätze entspricht natürlich dem Speichern einer Liste zugewiesener Speicherplätze.)


Es wird nicht einmal ein explizites Größenfeld benötigt. Es kann nur einen Zeiger auf den nächsten Block und ein zugewiesenes Bit haben.
Ninjalj

0

Nun, das einzige, was Sie brauchen, ist ein Zeiger, mit dem Sie den zuvor zugewiesenen Speicher freigeben. Die Anzahl der Bytes wird vom Betriebssystem verwaltet, sodass Sie sich darüber keine Sorgen machen müssen. Es wäre nicht notwendig, die Anzahl der zugewiesenen Bytes von free () zurückzugeben. Ich schlage Ihnen eine manuelle Methode vor, um die Anzahl der von einem laufenden Programm zugewiesenen Bytes / Positionen zu zählen:

Wenn Sie unter Linux arbeiten und wissen möchten, wie viele Bytes / Positionen malloc zugewiesen hat, können Sie ein einfaches Programm erstellen, das malloc ein- oder n-mal verwendet und die erhaltenen Zeiger druckt. Außerdem müssen Sie das Programm einige Sekunden lang in den Ruhezustand versetzen (genug, um Folgendes zu tun). Führen Sie danach das Programm aus, suchen Sie nach seiner PID, schreiben Sie cd / proc / process_PID und geben Sie einfach "cat maps" ein. Die Ausgabe zeigt Ihnen in einer bestimmten Zeile sowohl die Anfangs- als auch die Endspeicheradresse des Heap-Speicherbereichs (den, in dem Sie den Speicher dinamisch zuweisen). Wenn Sie die Zeiger auf diese zugewiesenen Speicherbereiche ausdrucken, werden Sie kann erraten, wie viel Speicher Sie zugewiesen haben.

Ich hoffe es hilft!


0

Warum sollte es? malloc () und free () ist absichtlich sehr einfache Memory - Management - Primitive und geordnete Speicherverwaltung in C ist weitgehend bis zum Entwickler. T.

Darüber hinaus macht realloc () dies bereits - wenn Sie die Zuordnung in realloc () reduzieren, werden die Daten nicht verschoben, und der zurückgegebene Zeiger entspricht dem Original.

Für die gesamte Standardbibliothek gilt im Allgemeinen, dass sie aus einfachen Grundelementen besteht, aus denen Sie komplexere Funktionen erstellen können, die Ihren Anwendungsanforderungen entsprechen. Die Antwort auf jede Frage der Form "Warum macht die Standardbibliothek kein X" ist, dass sie nicht alles kann, was ein Programmierer sich vorstellen kann (dafür sind Programmierer da), und daher nur sehr wenig tut - eigene erstellen oder Verwenden Sie Bibliotheken von Drittanbietern. Wenn Sie eine umfangreichere Standardbibliothek wünschen - einschließlich einer flexibleren Speicherverwaltung -, ist C ++ möglicherweise die Antwort.

Sie haben die Frage C ++ sowie C markiert, und wenn Sie C ++ verwenden, sollten Sie auf keinen Fall malloc / free verwenden - abgesehen von new / delete verwalten STL-Containerklassen den Speicher automatisch und auf eine wahrscheinliche Weise speziell auf die Art der verschiedenen Behälter abgestimmt sein.


Darüber hinaus macht realloc () das bereits - wenn Sie die Zuordnung in realloc () reduzieren, werden die Daten nicht verschoben, und der zurückgegebene Zeiger ist der gleiche wie der ursprüngliche. Ist das ein garantiertes Verhalten? Es scheint in mehreren eingebetteten Allokatoren üblich zu sein, aber ich war mir nicht sicher, ob dieses Verhalten als Teil von Standard-C angegeben wurde.
Rsaxvc

@rsaxvc: Gute Frage - cplusplus.com dokumentiert "Wenn die neue Größe größer ist, ist der Wert des neu zugewiesenen Teils unbestimmt." , was bedeutet, dass wenn es kleiner ist, es bestimmt ist . [opengroup.org () sagt: "Wenn die neue Größe des Speicherobjekts eine Bewegung des Objekts erfordern würde, wird der Platz für die vorherige Instanziierung des Objekts freigegeben." - Wenn es kleiner wäre, müssten die Daten nicht verschoben werden. Wiederum impliziert dies, dass kleinere nicht neu zugewiesen werden. Ich bin mir nicht sicher, was der ISO-Standard sagt.
Clifford

1
reallocist absolut erlaubt, die Daten zu verschieben. Gemäß dem C - Standard ist es völlig legal zu implementieren reallocals malloc+ memcpy+ free. Und es gibt gute Gründe, warum eine Implementierung eine verkleinerte Zuordnung verschieben möchte, z. B. um eine Fragmentierung des Speichers zu vermeiden.
Nathaniel J. Smith
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.