Suche…


Einführung

Für die Verwaltung von dynamisch zugewiesenem Speicher stellt die Standard-C-Bibliothek die Funktionen malloc() , calloc() , realloc() und free() calloc() . In C99 und später gibt es auch aligned_alloc() . Einige Systeme bieten auch alloca() .

Syntax

  • void * align_alloc (size_t-Ausrichtung, size_t-Größe); / * Erst seit C11 * /
  • void * calloc (size_t nelements, size_t size);
  • void free (void * ptr);
  • void * malloc (size_t size);
  • void * realloc (void * ptr, size_t size);
  • void * alloca (size_t size); / * von alloca.h, nicht standardmäßig, nicht portabel, gefährlich. * /

Parameter

Name Beschreibung
Größe ( malloc , realloc und aligned_alloc ) Gesamtgröße des Speichers in Byte. Für aligned_alloc muss die Größe ein ganzzahliges Vielfaches der Ausrichtung sein.
Größe ( calloc ) Größe jedes Elements
nelements Anzahl der Elemente
ptr Zeiger auf den zugewiesenen Speicher, der zuvor von malloc , calloc , realloc oder aligned_alloc
Ausrichtung Ausrichtung des zugewiesenen Speichers

Bemerkungen

C11

Beachten Sie, dass aligned_alloc() nur für C11 oder höher definiert ist.

Systeme wie die auf POSIX basierenden Systeme bieten andere Möglichkeiten zum posix_memalign() von ausgerichtetem Speicher (z. B. posix_memalign() ) sowie andere Speicherverwaltungsoptionen (z. B. mmap() ).

Speicher freigeben

Sie können dynamisch zugewiesenen Speicher freigeben, indem Sie free () aufrufen.

int *p = malloc(10 * sizeof *p); /* allocation of memory */
if (p == NULL) 
{
    perror("malloc failed");
    return -1;
}

free(p); /* release of memory */
/* note that after free(p), even using the *value* of the pointer p
   has undefined behavior, until a new value is stored into it. */

/* reusing/re-purposing the pointer itself */
int i = 42;
p = &i; /* This is valid, has defined behaviour */

Der Speicher, auf den p wird nach dem Aufruf von free() entweder von der libc-Implementierung oder vom zugrunde liegenden Betriebssystem zurückgefordert, sodass der Zugriff auf den freigegebenen Speicherblock über p zu undefiniertem Verhalten führt . Zeiger, die auf freigegebene Speicherelemente verweisen, werden im Allgemeinen als Schlenker bezeichnet und stellen ein Sicherheitsrisiko dar. Darüber hinaus gibt der C-Standard an, dass selbst der Zugriff auf den Wert eines baumelnden Zeigers undefiniertes Verhalten hat. Der Zeiger p selbst kann wie oben gezeigt neu bestimmt werden.

Bitte beachten Sie, dass Sie free() für Zeiger aufrufen können, die direkt von den Funktionen malloc() , calloc() , realloc() und aligned_alloc() , oder wo die Dokumentation Ihnen sagt, dass der Speicher auf diese Weise zugewiesen wurde (Funktionen wie strdup () sind bemerkenswerte Beispiele). Einen Zeiger freigeben, der

  • mit dem Operator & für eine Variable erhalten oder
  • in der Mitte eines zugewiesenen Blocks

ist verboten. Ein solcher Fehler wird normalerweise nicht von Ihrem Compiler diagnostiziert, führt jedoch die Programmausführung in einen undefinierten Zustand.

Es gibt zwei gängige Strategien, um solche Fälle von undefiniertem Verhalten zu verhindern.

Der erste und vorzuziehende ist einfach - haben p selbst nicht mehr zu existieren, wenn es nicht mehr benötigt wird, zum Beispiel:

if (something_is_needed())
{

    int *p = malloc(10 * sizeof *p);
    if (p == NULL) 
    {
        perror("malloc failed");
        return -1;
    }

    /* do whatever is needed with p */

    free(p);
}

Durch Aufrufen von free() direkt vor dem Ende des übergeordneten Blocks (dh des } ) ist p nicht mehr vorhanden. Der Compiler gibt bei jedem Versuch, p danach zu verwenden, einen Kompilierungsfehler aus.

Ein zweiter Ansatz besteht darin, den Zeiger selbst nach dem Freigeben des Speichers, auf den er zeigt, ungültig zu machen:

free(p);
p = NULL;     // you may also use 0 instead of NULL

Argumente für diesen Ansatz:

  • Auf vielen Plattformen führt der Versuch, einen Nullzeiger zu dereferenzieren, sofort zum Absturz: Segmentierungsfehler. Hier erhalten wir mindestens eine Stack-Ablaufverfolgung, die auf die Variable zeigt, die nach der Freigabe verwendet wurde.

    Ohne den Zeiger auf NULL , haben wir den Schlenker. Das Programm wird höchstwahrscheinlich immer noch abstürzen, aber später, weil der Speicher, auf den der Zeiger zeigt, unbemerkt beschädigt wird. Solche Fehler sind schwer zu verfolgen, da sie zu einem Aufrufstack führen können, der völlig unabhängig von dem ursprünglichen Problem ist.

    Dieser Ansatz folgt somit dem Fail-Fast-Konzept .

  • Es ist sicher, einen Nullzeiger freizugeben. Der C-Standard legt fest, dass free(NULL) keine Auswirkung hat:

    Die Funktion free bewirkt, dass der Raum, auf den ptr zeigt, freigegeben wird, dh für die weitere Zuweisung verfügbar ist. Wenn ptr ein Nullzeiger ist, wird keine Aktion ausgeführt. Andernfalls ist das Verhalten undefiniert, wenn das Argument nicht mit einem Zeiger calloc , der zuvor von der Funktion calloc , malloc oder realloc , oder wenn der Speicherplatz durch einen Aufruf von free oder realloc freigegeben wurde.

  • Manchmal kann der erste Ansatz nicht verwendet werden (z. B. Speicher wird in einer Funktion zugewiesen und viel später in einer völlig anderen Funktion freigegeben).

Speicher zuweisen

Standardzuteilung

Die dynamischen Speicherzuordnungsfunktionen für C sind im Header <stdlib.h> definiert. Wenn Sie Speicherplatz für ein Objekt dynamisch zuweisen möchten, kann der folgende Code verwendet werden:

int *p = malloc(10 * sizeof *p);
if (p == NULL) 
{
    perror("malloc() failed");
    return -1;
}

Dieser berechnet die Anzahl der Bytes, die zehn int s im Speicher belegen, fordert dann die Anzahl der Bytes von malloc und ordnet das Ergebnis (dh die Startadresse des gerade mit malloc erstellten malloc ) einem Zeiger mit dem Namen p .

Es ist sizeof , die Größe des zu verwendenden Speichers mithilfe von sizeof zu berechnen, da das Ergebnis von sizeof die Implementierung definiert wird (mit Ausnahme der Zeichentypen char , signed char und unsigned char , für die sizeof so definiert ist, dass sie immer 1 ergibt.

Da malloc die Anforderung möglicherweise nicht bedienen kann, gibt es möglicherweise einen Nullzeiger zurück. Es ist wichtig, dies zu überprüfen, um spätere Versuche zur Dereferenzierung des Nullzeigers zu verhindern.

Der mit malloc() dynamisch zugewiesene malloc() kann mit realloc() verkleinert oder mit free() freigegeben werden, wenn er nicht mehr benötigt wird.

Alternativ deklarieren Sie das int array[10]; würde die gleiche Menge an Speicher zuweisen. Wenn sie jedoch innerhalb einer Funktion ohne das Schlüsselwort static deklariert wird, ist sie nur in der Funktion, in der sie deklariert ist, und in den aufgerufenen Funktionen verwendbar (da das Array auf dem Stack zugewiesen wird und der Speicherplatz zur Wiederverwendung freigegeben wird die Funktion kehrt zurück). Wenn sie alternativ mit static innerhalb einer Funktion definiert ist oder außerhalb einer Funktion definiert ist, dann ist ihre Lebensdauer die Lebensdauer des Programms. Zeiger können auch von einer Funktion zurückgegeben werden, eine Funktion in C kann jedoch kein Array zurückgeben.

Nullspeicher

Der von malloc zurückgegebene malloc kann nicht mit einem angemessenen Wert initialisiert werden. Es sollte darauf geachtet werden, den Speicher mit memset auf Null zu memset oder einen geeigneten Wert sofort in ihn zu kopieren. Alternativ gibt calloc einen Block der gewünschten Größe zurück, in dem alle Bits auf 0 initialisiert werden. Dies muss nicht der Darstellung der Gleitkommazahl Null oder einer Nullzeiger-Konstante entsprechen.

int *p = calloc(10, sizeof *p);
if (p == NULL) 
{
    perror("calloc() failed");
    return -1;
}

Ein Hinweis zu calloc : Die meisten (häufig verwendeten) Implementierungen optimieren die Leistung von calloc() , sodass es schneller ist als das Aufrufen von malloc() und dann memset() , obwohl der Nettoeffekt identisch ist.

Ausgerichteter Speicher

C11

Mit C11 wurde eine neue Funktion aligned_alloc() die bei der angegebenen Ausrichtung Speicherplatz aligned_alloc() . Es kann verwendet werden, wenn der zuzuweisende Speicher an bestimmten Grenzen ausgerichtet werden muss, die von malloc() oder calloc() nicht erfüllt werden können. malloc() und calloc() weisen Speicher zu, der für jeden Objekttyp geeignet ausgerichtet ist (dh, die Ausrichtung ist alignof(max_align_t) ). Mit aligned_alloc() können jedoch größere Ausrichtungen angefordert werden.

/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
    perror("aligned_alloc()");
    return -1;
}
free(ptr);

Der C11-Standard enthält zwei Einschränkungen: 1) Die angeforderte Größe (zweites Argument) muss ein ganzzahliges Vielfaches der Ausrichtung sein (erstes Argument) und 2) der Wert der Ausrichtung sollte eine gültige Ausrichtung sein, die von der Implementierung unterstützt wird. Wenn eine der beiden Bedingungen nicht erfüllt wird, führt dies zu undefiniertem Verhalten .

Speicher neu zuordnen

Möglicherweise müssen Sie den Zeigerspeicherplatz vergrößern oder verkleinern, nachdem Sie ihm Speicher zugewiesen haben. Die Funktion void *realloc(void *ptr, size_t size) hebt das alte Objekt auf, auf das ptr und gibt einen Zeiger auf ein Objekt mit der durch size angegebenen size . ptr ist der Zeiger auf einen Speicherblock, der zuvor mit malloc , calloc oder realloc (oder einem Nullzeiger) zugewiesen wurde, um neu zuzuordnen. Der maximal mögliche Inhalt des ursprünglichen Speichers bleibt erhalten. Wenn die neue Größe größer ist, wird zusätzlicher Speicher, der über die alte Größe hinausgeht, nicht initialisiert. Wenn die neue Größe kürzer ist, geht der Inhalt des geschrumpften Teils verloren. Wenn ptr NULL ist, wird ein neuer Block zugewiesen und ein Zeiger darauf wird von der Funktion zurückgegeben.

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int *p = malloc(10 * sizeof *p);
    if (NULL == p) 
    {
        perror("malloc() failed");
        return EXIT_FAILURE;
    }
 
    p[0] = 42;
    p[9] = 15;

    /* Reallocate array to a larger size, storing the result into a
     * temporary pointer in case realloc() fails. */
    {
        int *temporary = realloc(p, 1000000 * sizeof *temporary);

        /* realloc() failed, the original allocation was not free'd yet. */
        if (NULL == temporary)
        {
            perror("realloc() failed");
            free(p); /* Clean up. */
            return EXIT_FAILURE;
        }      

        p = temporary;
    }

    /* From here on, array can be used with the new size it was 
     * realloc'ed to, until it is free'd. */

    /* The values of p[0] to p[9] are preserved, so this will print:
       42 15
    */
    printf("%d %d\n", p[0], p[9]);

    free(p);

    return EXIT_SUCCESS;
}

Das neu zugewiesene Objekt kann dieselbe Adresse wie *p haben oder nicht. Daher ist es wichtig, den Rückgabewert von realloc zu erfassen, der die neue Adresse enthält, wenn der Aufruf erfolgreich ist.

Stellen Sie sicher, dass Sie den Rückgabewert von realloc einem temporary statt dem ursprünglichen p zuweisen. realloc gibt bei einem Fehler den realloc null zurück, wodurch der Zeiger überschrieben wird. Dies würde Ihre Daten verlieren und einen Speicherverlust verursachen.

Mehrdimensionale Arrays mit variabler Größe

C99

Seit C99 besitzt C Arrays mit variabler Länge (VLA), die Arrays mit Grenzen bilden, die nur zur Initialisierungszeit bekannt sind. Während Sie darauf achten müssen, nicht zu große VLA zuzuordnen (sie könnten Ihren Stack zerstören), ist die Verwendung von Zeigern auf VLA und deren Verwendung in sizeof Ausdrücken in Ordnung.

double sumAll(size_t n, size_t m, double A[n][m]) {
    double ret = 0.0;
    for (size_t i = 0; i < n; ++i)
       for (size_t j = 0; j < m; ++j)
          ret += A[i][j]
    return ret;
}

int main(int argc, char *argv[argc+1]) {
   size_t n = argc*10;
   size_t m = argc*8;
   double (*matrix)[m] = malloc(sizeof(double[n][m]));
   // initialize matrix somehow
   double res = sumAll(n, m, matrix);
   printf("result is %g\n", res);
   free(matrix);
}

Hier ist die matrix ein Zeiger auf Elemente vom Typ double[m] , und der Ausdruck sizeof mit double[n][m] stellt sicher, dass in ihm Platz für n solche Elemente ist.

Dieser gesamte Speicherplatz wird zusammenhängend zugewiesen und kann daher durch einen einzigen Anruf für free freigegeben werden.

Das Vorhandensein von VLA in der Sprache beeinflusst auch die möglichen Deklarationen von Arrays und Zeigern in Funktionsheadern. In den Array-Parametern [] ist jetzt ein allgemeiner ganzzahliger Ausdruck zulässig. Für beide Funktionen verwenden die Ausdrücke in [] Parameter, die zuvor in der Parameterliste deklariert wurden. Für sumAll sind dies die Längen, die der Benutzercode für die Matrix erwartet. Wie bei allen Array-Funktionsparametern in C wird die innerste Dimension in einen Zeigertyp umgeschrieben. Dies entspricht der Deklaration

  double sumAll(size_t n, size_t m, double (*A)[m]);

Das heißt, n ist nicht wirklich Teil der Funktionsschnittstelle, aber die Informationen können für die Dokumentation nützlich sein und könnten auch von Compilern zur Überprüfung von Grenzen verwendet werden, um vor einem Zugriff außerhalb der Grenzen zu warnen.

Ebenso ist für main der Ausdruck argc+1 die minimale Länge, die der C-Standard für das Argument argv vorschreibt.

Beachten Sie, dass die Unterstützung für VLA in C11 offiziell optional ist, aber wir kennen keinen Compiler, der C11 implementiert und für den sie nicht verfügbar ist. Sie können mit dem Makro __STDC_NO_VLA__ wenn Sie müssen.

Realloc (Ptr, 0) ist nicht gleich "Free" (Ptr)

realloc ist konzeptionell gleichbedeutend mit malloc + memcpy + free auf dem anderen Zeiger.

Wenn der angeforderte Speicherplatz null ist, ist das Verhalten von realloc implementierungsdefiniert. Dies ist ähnlich für alle Speicherzuordnungsfunktionen , die eine Empfangs size Parameter Wert 0 . Solche Funktionen können zwar einen Nicht-Null-Zeiger zurückgeben, dieser darf jedoch niemals dereferenziert werden.

Daher ist realloc(ptr,0) nicht mit free(ptr) gleichwertig. Es kann

  • Seien Sie eine "faule" Implementierung und geben Sie einfach ptr
  • free(ptr) , ordnen Sie ein Dummy-Element zu und geben Sie dieses zurück
  • free(ptr) und 0
  • Gib einfach 0 für einen Fehler zurück und tue nichts anderes.

So sind insbesondere die beiden letztgenannten Fälle durch den Anwendungscode nicht zu unterscheiden.

Dies bedeutet, dass realloc(ptr,0) den Speicher möglicherweise nicht wirklich freigibt / freigibt. Daher sollte er niemals als Ersatz für free .

Benutzerdefinierte Speicherverwaltung

malloc() ruft häufig zugrunde liegende Betriebssystemfunktionen auf, um Speicherseiten zu erhalten. Die Funktion ist jedoch nichts Besonderes, und sie kann in Straight C implementiert werden, indem ein großes statisches Array deklariert und von diesem zugewiesen wird (es gibt eine leichte Schwierigkeit, die korrekte Ausrichtung zu gewährleisten. In der Praxis ist das Anpassen an 8 Byte fast immer ausreichend).

Um ein einfaches Schema zu implementieren, wird ein Steuerblock in dem Speicherbereich unmittelbar vor dem Zeiger gespeichert, der vom Anruf zurückgegeben werden soll. Dies bedeutet, dass free() implementiert werden kann, indem von dem zurückgegebenen Zeiger subtrahiert wird und die Steuerinformationen abgelesen werden. Hierbei handelt es sich normalerweise um die Blockgröße plus einige Informationen, die es ermöglichen, dass sie in die freie Liste zurückgegeben werden - eine verknüpfte Liste nicht zugeordneter Blöcke.

Wenn der Benutzer eine Zuweisung anfordert, wird die freie Liste durchsucht, bis ein Block gefunden wird, der mit dem angeforderten Betrag identisch oder größer ist. Anschließend wird er gegebenenfalls aufgeteilt. Dies kann zu einer Fragmentierung des Speichers führen, wenn der Benutzer ständig viele Zuordnungen und Freigaben von unvorhersehbarer Größe vornimmt und in unvorhersehbaren Intervallen (nicht alle realen Programme verhalten sich so, das einfache Schema ist für kleine Programme oft ausreichend).

/* typical control block */
struct block
{
   size_t size;         /* size of block */
   struct block *next;  /* next block in free list */ 
   struct block *prev;  /* back pointer to previous block in memory */
   void *padding;       /* need 16 bytes to make multiple of 8 */
}

static struct block arena[10000]; /* allocate from here */
static struct block *firstfree;

Viele Programme erfordern eine große Anzahl von Zuweisungen von kleinen Objekten gleicher Größe. Dies ist sehr einfach zu implementieren. Verwenden Sie einfach einen Block mit einem nächsten Zeiger. Wenn also ein Block von 32 Bytes erforderlich ist:

union block
{
   union block * next;
   unsigned char payload[32];
}  

static union block arena[100];
static union block * head; 
void init(void)
{
    int i;
    for (i = 0; i < 100 - 1; i++)
        arena[i].next = &arena[i + 1];
    arena[i].next = 0; /* last one, null */
    head = &block[0];
}
 
void *block_alloc()
{
    void *answer = head;
    if (answer)
        head = head->next;
    return answer;
}

void block_free(void *ptr)
{
    union block *block = ptr;
    block->next = head;
    head - block;
}

Dieses Schema ist extrem schnell und effizient und kann mit einem gewissen Verlust an Klarheit generisch gemacht werden.

Allocationa: Speicher auf Stack zuweisen

alloca : alloca wird hier nur der Vollständigkeit halber erwähnt. Es ist vollständig nicht portabel (nicht durch eine der gängigen Standards abgedeckt) und verfügt über eine Reihe potenziell gefährlicher Funktionen, die es für Unbewusste unsicher machen. Moderner C-Code sollte ihn durch Arrays mit variabler Länge (VLA) ersetzen.

Manuelle Seite

#include <alloca.h>
// glibc version of stdlib.h include alloca.h by default

void foo(int size) {
    char *data = alloca(size);
    /*
      function body;
    */
    // data is automatically freed
}

Weisen Sie dem Stack-Frame des Aufrufers Speicher zu. Der vom zurückgegebenen Zeiger referenzierte Speicherplatz wird automatisch freigegeben , wenn die Caller-Funktion abgeschlossen ist.

Während diese Funktion für die automatische Speicherverwaltung geeignet ist, sollten Sie beachten, dass das Anfordern einer großen Zuweisung einen Stapelüberlauf verursachen kann und dass Sie den mit alloca zugewiesenen Speicher nicht free alloca (was zu mehr alloca beim Stapelüberlauf führen kann).

Aus diesem Grund wird die Verwendung von alloca innerhalb einer Schleife oder eine rekursive Funktion nicht empfohlen.

Und da der Speicher bei der Funktionsrückgabe free , können Sie den Zeiger nicht als Funktionsergebnis zurückgeben ( das Verhalten wäre undefiniert ).

Zusammenfassung

  • Call identisch mit malloc
  • automatisch bei Funktionsrückkehr frei
  • nicht kompatibel mit free realloc Funktionen ( undefiniertes Verhalten )
  • Zeiger kann nicht als Funktionsergebnis zurückgegeben werden ( undefiniertes Verhalten )
  • Zuordnungsgröße begrenzt durch Stack-Speicherplatz, der (bei den meisten Computern) viel kleiner ist als der für malloc() verfügbare Heap- malloc()
  • Vermeiden Sie die Verwendung alloca() und VLAs (Arrays mit variabler Länge) in einer einzigen Funktion
  • alloca() ist nicht so portabel wie malloc() et al

Empfehlung

  • Verwenden alloca() in neuem Code nicht alloca()
C99

Moderne Alternative.

void foo(int size) {
    char data[size];
    /*
      function body;
    */
    // data is automatically freed
}

Dies funktioniert an den alloca() denen alloca() , und funktioniert an Stellen, an denen die alloca() nicht innerhalb von Schleifen alloca() . Es wird davon ausgegangen, dass entweder eine C99-Implementierung oder eine C11-Implementierung __STDC_NO_VLA__ die __STDC_NO_VLA__ nicht definiert.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow