Array oder Malloc?

12

Ich verwende den folgenden Code in meiner Anwendung, und es funktioniert gut. Aber ich frage mich, ob es besser ist, es mit Malloc zu machen oder es so zu lassen, wie es ist?

function (int len)
{
char result [len] = some chars;
send result over network
}
Dev Bag
quelle
2
Wird davon ausgegangen, dass der Code für eine nicht eingebettete Umgebung bestimmt ist?
Tehnyit

Antworten:

27

Der Hauptunterschied besteht darin, dass VLAs (Variable Length Arrays) keinen Mechanismus zum Erkennen von Zuordnungsfehlern bieten.

Wenn Sie erklären

char result[len];

und lenübersteigt die Menge an verfügbarem Stapelspeicher, Ihr Programm das Verhalten nicht definiert ist. Es gibt keinen Sprachmechanismus, um im Voraus zu bestimmen, ob die Zuweisung erfolgreich sein wird, oder um nach der Tatsache zu bestimmen, ob sie erfolgreich ist.

Auf der anderen Seite, wenn Sie schreiben:

char *result = malloc(len);
if (result == NULL) {
    /* allocation failed, abort or take corrective action */
}

Dann können Sie Fehler ordnungsgemäß behandeln oder zumindest sicherstellen, dass Ihr Programm nach einem Fehler nicht versucht, die Ausführung fortzusetzen.

(Nun, meistens. Auf Linux-Systemen malloc()kann ein Teil des Adressraums zugewiesen werden, auch wenn kein entsprechender Speicher verfügbar ist. Spätere Versuche, diesen Speicherplatz zu verwenden, können den OOM Killer auslösen . Die Überprüfung auf malloc()Fehler ist jedoch nach wie vor empfehlenswert .)

Ein weiteres Problem ist, dass auf vielen Systemen mehr Platz (möglicherweise viel mehr) zur Verfügung steht malloc()als für automatische Objekte wie VLAs.

Und wie Philipps Antwort bereits erwähnte, wurden VLAs in C99 hinzugefügt (Microsoft unterstützt sie insbesondere nicht).

Und VLAs wurden in C11 optional gemacht. Wahrscheinlich werden die meisten C11-Compiler sie unterstützen, aber Sie können sich nicht darauf verlassen.

Keith Thompson
quelle
14

Automatische Arrays mit variabler Länge wurden in C99 in C eingeführt.

Sofern Sie keine Bedenken hinsichtlich der Abwärtsvergleichbarkeit mit älteren Standards haben, ist dies in Ordnung.

Wenn es funktioniert, berühren Sie es im Allgemeinen nicht. Optimieren Sie nicht im Voraus. Machen Sie sich keine Sorgen, wenn Sie spezielle Funktionen oder clevere Methoden hinzufügen, da Sie diese häufig nicht verwenden werden. Halte es einfach.

Philip
quelle
7
Ich muss mit dem Sprichwort "Wenn es funktioniert, fass es nicht an" nicht einverstanden sein. Wenn Sie fälschlicherweise glauben, dass ein Code "funktioniert", können Sie Probleme mit einem Code umgehen, der "funktioniert". Der Glaube muss durch die vorläufige Annahme ersetzt werden, dass ein Code gerade funktioniert.
Bruce Ediger
2
Fass es nicht an, bis du eine Version erstellt hast, die vielleicht besser "funktioniert" ...
H_7
8

Wenn Ihr Compiler Arrays mit variabler Länge unterstützt, besteht die einzige Gefahr darin, den Stapel auf einigen Systemen zu überlaufen, wenn der Stapel lenlächerlich groß ist. Wenn Sie sicher sind, dass dieser lenWert nicht größer als eine bestimmte Zahl sein wird, und Sie wissen, dass Ihr Stapel auch bei maximaler Länge nicht überläuft, lassen Sie den Code unverändert. Ansonsten schreiben Sie es mit mallocund um free.

dasblinkenlight
quelle
was ist mit dieser Funktion auf Nicht-C99 (char []) {char result [sizeof (char)] = einige Zeichen; Ergebnis über Netzwerk senden}
Dev Bag
@DevBag char result [sizeof(char)]ist ein Array mit einer Größe 1(da sizeof(char)gleich eins), sodass die Zuweisung abgeschnitten wird some chars.
dasblinkenlight
Entschuldigung, ich meine es so function (char str []) {char result [sizeof (str)] = ein paar Zeichen; Ergebnis über Netzwerk senden}
Dev Bag
4
@DevBag Dies wird auch nicht funktionieren - Zerfällt str zu einem Zeiger , so sizeofdass es vier oder acht sein wird, abhängig von der Zeigergröße auf Ihrem System.
dasblinkenlight
2
Wenn Sie eine Version von C ohne Arrays mit variabler Länge verwenden, können Sie möglicherweise char* result = alloca(len);die Zuordnung auf dem Stapel vornehmen. Es hat den gleichen grundlegenden Effekt (und die gleichen grundlegenden Probleme)
Gort the Robot
6

Ich mag die Idee, dass Sie ein zur Laufzeit zugewiesenes Array ohne Speicherfragmentierung, baumelnde Zeiger usw. haben können. Andere haben jedoch darauf hingewiesen, dass diese Zuweisung zur Laufzeit unbemerkt fehlschlagen kann. Also habe ich das mit gcc 4.5.3 in einer Cygwin-Bash-Umgebung versucht:

#include <stdio.h>
#include <string.h>

void testit (unsigned long len)
{
    char result [len*2];
    char marker[100];

    memset(marker, 0, sizeof(marker));
    printf("result's size: %lu\n", sizeof(result));
    strcpy(result, "this is a test that should overflow if no allocation");
    printf("marker's contents: '%s'\n", marker);
}

int main(int argc, char *argv[])
{
    testit(100);
    testit((unsigned long)-1);  // probably too big
}

Die Ausgabe war:

$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'

Die zu große Länge, die beim zweiten Aufruf übergeben wurde, verursachte eindeutig den Fehler (Überlaufen in marker []). Dies bedeutet nicht, dass diese Art der Überprüfung narrensicher ist (Dummköpfe können schlau sein!) Oder dass sie den Standards von C99 entspricht, aber es könnte hilfreich sein, wenn Sie diese Bedenken haben.

Wie immer YMMV.

Harold Bamford
quelle
1
+1 das ist sehr nützlich: 3
Kokizzu
Es ist immer schön, Code zu haben, der den Behauptungen der Leute entspricht! Danke ^ _ ^
Musa Al-hassy
3

Im Allgemeinen ist der Stapel der einfachste und beste Ort, um Ihre Daten zu speichern.

Ich würde die Probleme von VLAs vermeiden, indem ich einfach das größte Array zuteile, das Sie erwarten.

Es gibt jedoch Fälle, in denen der Haufen am besten ist und sich die Mühe lohnt, mit Malloc herumzuspielen.

  1. Bei seiner großen aber variablen Datenmenge. Groß hängt von Ihrer Umgebung ab> 1 KB für eingebettete Systeme,> 10 MB für einen Enterprise-Server.
  2. Wenn Sie möchten, dass die Daten nach dem Verlassen Ihrer Routine erhalten bleiben, z. B. wenn Sie einen Zeiger auf Ihre Daten zurückgeben. Verwenden
  3. Eine Kombination aus statischem Zeiger und malloc () ist normalerweise besser als die Definition eines großen statischen Arrays.
James Anderson
quelle
3

In der eingebetteten Programmierung verwenden wir immer statische Arrays anstelle von malloc, wenn die malloc- und freien Operationen häufig sind. Aufgrund des Mangels an Speicherverwaltung im eingebetteten System verursachen die häufigen Zuweisungs- und Freigabevorgänge Speicherfragmente. Wir sollten jedoch einige knifflige Methoden anwenden, z. B. das Definieren der maximalen Größe eines Arrays und das Verwenden eines statischen lokalen Arrays.

Wenn Ihre Anwendung unter Linux oder Windows ausgeführt wird, spielt es keine Rolle, Array oder Malloc zu verwenden. Der entscheidende Punkt liegt darin, wo Sie Ihre Datumsstruktur und Ihre Codelogik verwenden.

Steven Mou
quelle
1

Was bisher noch niemand erwähnt hat, ist, dass die Array-Option mit variabler Länge wahrscheinlich wesentlich schneller als malloc / free sein wird, da das Zuweisen einer VLA nur ein Fall ist, in dem der Stapelzeiger angepasst wird (zumindest in GCC).

Wenn diese Funktion häufig aufgerufen wird (was Sie natürlich durch die Profilerstellung bestimmen), ist die VLA eine gute Optimierungsoption.

JeremyP
quelle
1
Es wird bis zu dem Punkt gut erscheinen, an dem es Sie in eine Situation außerhalb des Stapels zwingt. Darüber hinaus ist es möglicherweise nicht Ihr Code, der das Stack-Limit tatsächlich erreicht . es könnte in einer Bibliothek oder einem Systemaufruf (oder einer Unterbrechung) beißen.
Donal Fellows
@Donal Performance ist immer ein Kompromiss zwischen Speicher und Geschwindigkeit. Wenn Sie Arrays von mehreren Megabyte zuweisen, haben Sie einen Punkt, selbst für einige Kilobyte, solange die Funktion nicht rekursiv ist. Dies ist eine gute Optimierung.
JeremyP
1

Dies ist eine sehr häufige C-Lösung, die ich für das Problem verwende und die möglicherweise hilfreich ist. Im Gegensatz zu VLAs besteht in pathologischen Fällen kein praktisches Risiko eines Stapelüberlaufs.

/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
    /// Stores raw bytes for fast access.
    char fast_mem[512];

    /// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
    /// dynamically allocated memory address.
    void* data;
};

/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
    // Utilize the stack if the memory fits, otherwise malloc.
    mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
    return mem->data;
}

/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
    // Free the memory if it was allocated dynamically with 'malloc'.
    if (mem->data != mem->fast_mem)
        free(mem->data);
    mem->data = 0;
}

So verwenden Sie es in Ihrem Fall:

struct FastMem fm;

// `result` will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);

// send result over network.
...

// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);

In diesem Fall wird der Stapel verwendet, wenn die Zeichenfolge in maximal 512 Byte passt. Andernfalls wird eine Heap-Zuordnung verwendet. Dies kann nützlich sein, wenn die Zeichenfolge beispielsweise in 99% der Fälle in maximal 512 Byte passt. Nehmen wir jedoch an, es gibt einen verrückten exotischen Fall, den Sie gelegentlich behandeln müssen, wenn die Zeichenfolge 32 Kilobyte lang ist und der Benutzer auf seiner Tastatur eingeschlafen ist oder so. Dadurch können beide Situationen problemlos bewältigt werden.

Die aktuelle Version, die ich in der Produktion verwende, hat auch eine eigene Version von reallocundcalloc so weiter sowie standardkonforme C ++ - Datenstrukturen, die auf demselben Konzept basieren, aber ich habe das zur Veranschaulichung des Konzepts erforderliche Minimum extrahiert.

Es hat den Vorbehalt, dass das Kopieren gefährlich ist, und Sie sollten keine Zeiger zurückgeben, die durch das Kopieren zugewiesen wurden (sie könnten ungültig werden, da die FastMemInstanz zerstört wird). Es ist für einfache Fälle innerhalb des Bereichs einer lokalen Funktion gedacht, in denen Sie versucht wären, immer die Stapel- / VLAs zu verwenden, da in seltenen Fällen Puffer- / Stapelüberläufe auftreten können. Es ist kein Allokator für allgemeine Zwecke und sollte nicht als solcher verwendet werden.

Ich habe es tatsächlich vor langer Zeit als Antwort auf eine Situation in einer alten Codebasis mit C89 erstellt, in der ein ehemaliges Team dachte, dass es niemals passieren würde, wenn ein Benutzer einen Gegenstand mit einem Namen benennt, der mehr als 2047 Zeichen lang ist (vielleicht ist er auf seiner Tastatur eingeschlafen) ). Meine Kollegen haben tatsächlich versucht, die Größe der an verschiedenen Stellen zugewiesenen Arrays auf 16.384 zu erhöhen. Zu diesem Zeitpunkt hielt ich es für lächerlich und tauschte lediglich ein höheres Risiko eines Stapelüberlaufs gegen ein geringeres Risiko eines Pufferüberlaufs aus. Dies stellte eine Lösung dar, die sehr einfach zu installieren war, um diese Fälle zu beheben, indem nur ein paar Codezeilen hinzugefügt wurden. Dies ermöglichte es, den allgemeinen Fall sehr effizient zu handhaben und den Stapel dennoch zu nutzen, ohne die verrückten seltenen Fälle, bei denen der Haufen die Software zum Absturz brachte. Wie auch immer, ich' Wir fanden es seitdem auch nach C99 nützlich, da VLAs uns immer noch nicht vor Stapelüberläufen schützen können. Dieser kann aber noch Pools aus dem Stack für kleine Zuweisungsanfragen bilden.


quelle
1

Der Call-Stack ist immer begrenzt. Unter Mainstream-Betriebssystemen wie Linux oder Windows liegt die Grenze bei einem oder wenigen Megabyte (und Sie könnten Möglichkeiten finden, dies zu ändern). Bei einigen Multithread-Anwendungen ist der Wert möglicherweise niedriger (da die Threads mit einem kleineren Stapel erstellt werden könnten). Auf eingebetteten Systemen können es nur wenige Kilobyte sein. Eine gute Faustregel ist, Call-Frames zu vermeiden, die größer als ein paar Kilobyte sind.

Die Verwendung einer VLA ist nur dann sinnvoll, wenn Sie sicher sind, dass Ihre VLAlen klein genug ist (höchstens einige Dutzendtausende). Ansonsten haben Sie einen Stapelüberlauf und das ist ein Fall von undefiniertem Verhalten , sehr beängstigend Situation.

Die Verwendung der manuellen dynamischen C-Speicherzuweisung (z. B. callocoder malloc&free ) hat jedoch auch Nachteile:

  • es kann fehlschlagen und Sie sollten immer auf Fehler prüfen (z . B. callocoder malloczurück NULL).

  • Es ist langsamer: Eine erfolgreiche VLA-Zuweisung dauert einige Nanosekunden, eine erfolgreiche mallockann einige Mikrosekunden (in guten Fällen nur einen Bruchteil einer Mikrosekunde) oder sogar mehr (in pathologischen Fällen, in denen es zu einem Thrashing kommt , viel mehr) benötigen .

  • Es ist viel schwieriger zu codieren: Sie können freenur, wenn Sie sicher sind, dass die spitze Zone nicht mehr verwendet wird. In Ihrem Fall können Sie beide callocund freedieselbe Routine aufrufen .

Wenn Sie wissen, dass Ihr result Name (ein sehr schlechter Name, Sie sollten niemals die Adresse einer automatischen Variablen- VLA zurückgeben; ich verwende ihn bufstatt resultunten) meistens klein ist, können Sie ihn in Sonderfällen verwenden, z

char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf) 
  free(buf);

Der obige Code ist jedoch weniger lesbar und ist wahrscheinlich eine vorzeitige Optimierung. Es ist jedoch robuster als eine reine VLA-Lösung.

PS. Einige Systeme (z. B. einige Linux-Distributionen sind standardmäßig aktiviert) haben eine Überbelegung des Arbeitsspeichers (was dazu führt, mallocdass ein Zeiger angegeben wird, auch wenn nicht genügend Arbeitsspeicher vorhanden ist). Dies ist eine Funktion, die ich auf meinen Linux-Computern nicht mag und normalerweise deaktiviere.

Basile Starynkevitch
quelle