Warum wird die Verwendung von alloca () nicht als bewährte Methode angesehen?

401

alloca()ordnet Speicher eher auf dem Stapel als auf dem Heap zu, wie im Fall von malloc(). Wenn ich von der Routine zurückkehre, wird der Speicher freigegeben. Tatsächlich löst dies mein Problem, dynamisch zugewiesenen Speicher freizugeben. Die Freigabe des zugewiesenen Speichers bereitet malloc()große Kopfschmerzen und führt, wenn sie irgendwie übersehen wird, zu allen möglichen Speicherproblemen.

Warum wird von der Verwendung alloca()trotz der oben genannten Merkmale abgeraten?

Vaibhav
quelle
40
Nur eine schnelle Anmerkung. Obwohl diese Funktion in den meisten Compilern enthalten ist, wird sie vom ANSI-C-Standard nicht benötigt und kann daher die Portabilität einschränken. Eine andere Sache ist, dass Sie nicht dürfen! free () den Zeiger, den Sie erhalten, und er wird automatisch freigegeben, nachdem Sie die Funktion verlassen haben.
Merkuro
9
Außerdem wird eine Funktion mit alloca () nicht inline gesetzt, wenn sie als solche deklariert wird.
Justicle
2
@Justicle, kannst du eine Erklärung geben? Ich bin sehr neugierig, was hinter diesem Verhalten
steckt
47
Vergessen Sie das Rauschen in Bezug auf die Portabilität, die Notwendigkeit, einen Anruf zu tätigen free(was offensichtlich von Vorteil ist), die Nicht-Inline-Fähigkeit (offensichtlich sind die Heap-Zuweisungen sehr viel schwerer) usw. Der einzige Grund, dies zu vermeiden, allocasind große Größen. Das heißt, Tonnen von Stapelspeicher zu verschwenden ist keine gute Idee, und Sie haben die Möglichkeit eines Stapelüberlaufs. Wenn dies der Fall ist, sollten Sie malloca/freea
valdo
5
Ein weiterer positiver Aspekt allocaist, dass der Stapel nicht wie der Heap fragmentiert werden kann. Dies könnte sich für harte Echtzeitanwendungen im Run-Forever-Stil oder sogar für sicherheitskritische Anwendungen als nützlich erweisen, da die WCRU dann statisch analysiert werden kann, ohne auf benutzerdefinierte Speicherpools mit eigenen Problemen zurückzugreifen (keine zeitliche Lokalität, suboptimale Ressource) verwenden).
Andreas

Antworten:

245

Die Antwort ist genau dort auf der manSeite (zumindest unter Linux ):

RETURN VALUE Die Funktion alloca () gibt einen Zeiger auf den Anfang des zugewiesenen Speicherplatzes zurück. Wenn die Zuordnung einen Stapelüberlauf verursacht, ist das Programmverhalten undefiniert.

Was nicht heißt, dass es niemals verwendet werden sollte. Eines der OSS-Projekte, an denen ich arbeite, verwendet es ausgiebig, und solange Sie es nicht missbrauchen (mit allocariesigen Werten), ist es in Ordnung. Sobald Sie die Marke "einige hundert Bytes" überschritten haben, ist es Zeit malloc, stattdessen "Freunde" zu verwenden. Möglicherweise erhalten Sie immer noch Zuordnungsfehler, aber zumindest haben Sie einige Hinweise auf den Fehler, anstatt nur den Stapel auszublasen.

Sean Bright
quelle
35
Es gibt also wirklich kein Problem damit, dass Sie nicht auch große Arrays deklarieren würden?
TED
88
@ Sean: Ja, das Risiko eines Stapelüberlaufs ist der angegebene Grund, aber dieser Grund ist ein bisschen albern. Erstens, weil (wie Vaibhav sagt) große lokale Arrays genau das gleiche Problem verursachen, aber bei weitem nicht so verleumdet sind. Ebenso kann eine Rekursion den Stapel genauso leicht sprengen. Tut mir leid, aber ich bitte Sie, der vorherrschenden Idee, dass der auf der Manpage angegebene Grund gerechtfertigt ist, hoffentlich entgegenzuwirken.
j_random_hacker
49
Mein Punkt ist, dass die in der Manpage angegebene Begründung keinen Sinn ergibt, da alloca () genauso "schlecht" ist wie die anderen Dinge (lokale Arrays oder rekursive Funktionen), die als koscher gelten.
j_random_hacker
39
@ninjalj: Nicht von sehr erfahrenen C / C ++ - Programmierern, aber ich denke, dass viele Leute, die Angst alloca()haben, nicht die gleiche Angst vor lokalen Arrays oder Rekursionen haben (tatsächlich werden viele Leute, die nach unten schreien, alloca()die Rekursion loben, weil sie "elegant aussieht"). . Ich stimme Shauns Rat zu ("alloca () ist in Ordnung für kleine Allokationen"), aber ich bin nicht einverstanden mit der Einstellung, die alloca () als einzigartig böse unter den 3 einstuft - sie sind gleichermaßen gefährlich!
j_random_hacker
35
Hinweis: Linux „optimistisch“ Memory Allocation - Strategie gegeben, Sie sehr wahrscheinlich nicht alle Anzeichen für ein heap-Erschöpfung Scheitern ... statt malloc () geben Ihnen einen schönen Nicht-NULL - Zeiger, und dann , wenn Sie tatsächlich versuchen, Wenn Sie auf den Adressraum zugreifen, auf den es verweist, wird Ihr Prozess (oder ein anderer Prozess, unvorhersehbar) vom OOM-Killer getötet. Natürlich ist dies eher eine "Funktion" von Linux als ein C / C ++ - Problem an sich, aber es ist etwas zu beachten, wenn darüber diskutiert wird, ob alloca () oder malloc () "sicherer" ist. :)
Jeremy Friesner
209

Einer der denkwürdigsten Fehler, den ich hatte, war die Verwendung einer Inline-Funktion, die verwendet wurde alloca. Es manifestierte sich als Stapelüberlauf (weil es auf dem Stapel zugeordnet ist) an zufälligen Punkten der Programmausführung.

In der Header-Datei:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

In der Implementierungsdatei:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

Was also geschah, war die Compiler-Inline- DoSomethingFunktion, und alle Stapelzuweisungen erfolgten innerhalb der Process()Funktion und sprengten so den Stapel in die Luft . Zu meiner Verteidigung (und ich war nicht derjenige, der das Problem gefunden hat; ich musste zu einem der leitenden Entwickler gehen und weinen, wenn ich es nicht beheben konnte), es war nicht gerade alloca, es war eine der ATL-String-Konvertierungen Makros.

Die Lektion lautet also: Verwenden allocaSie sie nicht in Funktionen, von denen Sie glauben, dass sie inline sind.

Igor Zevaka
quelle
91
Interessant. Aber wäre das nicht ein Compiler-Fehler? Immerhin hat das Inlining das Verhalten des Codes geändert (es hat die Freigabe des mit alloca zugewiesenen Speicherplatzes verzögert).
Sleske
60
Anscheinend wird zumindest GCC dies berücksichtigen: "Beachten Sie, dass bestimmte Verwendungen in einer Funktionsdefinition sie für die Inline-Substitution ungeeignet machen können. Zu diesen Verwendungen gehören: Verwendung von Varargs, Verwendung von Allokation, [...]". gcc.gnu.org/onlinedocs/gcc/Inline.html
sleske
136
Welchen Compiler haben Sie geraucht?
Thomas Eding
22
Was ich nicht verstehe, ist, warum der Compiler den Bereich nicht gut nutzt, um festzustellen, ob Zuordnungen im Unterbereich mehr oder weniger "freigegeben" sind: Der Stapelzeiger könnte vor dem Aufrufen des Bereichs zu seinem Punkt zurückkehren, wie dies bei wann der Fall ist Rückkehr von der Funktion (nicht
wahr
7
Ich habe abgestimmt, aber die Antwort ist gut geschrieben: Ich stimme anderen zu, dass Sie die Zuordnung für einen eindeutig Compiler-Fehler bemängeln . Der Compiler hat bei einer Optimierung, die er nicht hätte vornehmen sollen, eine fehlerhafte Annahme getroffen. Das Umgehen eines Compiler-Fehlers ist in Ordnung, aber ich würde nichts dafür bemängeln, außer den Compiler.
Evan Carroll
75

Alte Frage, aber niemand erwähnte, dass es durch Arrays variabler Länge ersetzt werden sollte.

char arr[size];

anstatt

char *arr=alloca(size);

Es ist im Standard C99 enthalten und existierte in vielen Compilern als Compiler-Erweiterung.

Patrick Schlüter
quelle
5
Es wird von Jonathan Leffler in einem Kommentar zu Arthur Ulfeldts Antwort erwähnt.
Ninjalj
2
In der Tat, aber es zeigt auch, wie leicht es übersehen wird, da ich es nicht gesehen hatte, obwohl ich alle Antworten vor dem Posten gelesen hatte.
Patrick Schlüter
6
Ein Hinweis: Dies sind Arrays mit variabler Länge, keine dynamischen Arrays. Letztere können in der Größe geändert und normalerweise auf dem Heap implementiert werden.
Tim
1
Visual Studio 2015, das einige C ++ - kompiliert, hat das gleiche Problem.
Ahcox
2
Linus Torvalds mag keine VLAs im Linux-Kernel . Ab Version 4.20 sollte Linux fast VLA-frei sein.
Cristian Ciupitu
60

alloca () ist sehr nützlich, wenn Sie eine lokale Standardvariable nicht verwenden können, da ihre Größe zur Laufzeit bestimmt werden müsste und Sie absolut garantieren können, dass der Zeiger, den Sie von alloca () erhalten, NIEMALS verwendet wird, nachdem diese Funktion zurückgegeben wurde .

Sie können ziemlich sicher sein, wenn Sie

  • Geben Sie den Zeiger oder etwas, das ihn enthält, nicht zurück.
  • Speichern Sie den Zeiger nicht in einer auf dem Heap zugewiesenen Struktur
  • Lassen Sie keinen anderen Thread den Zeiger verwenden

Die wirkliche Gefahr besteht darin, dass irgendwann später jemand anderes gegen diese Bedingungen verstößt. In diesem Sinne ist es großartig, Puffer an Funktionen zu übergeben, die Text in sie formatieren :)

Arthur Ulfeldt
quelle
12
Die VLA-Funktion (Variable Length Array) von C99 unterstützt lokale Variablen mit dynamischer Größe, ohne dass explizit alloca () verwendet werden muss.
Jonathan Leffler
2
ordentlich! Weitere Informationen finden Sie in Abschnitt '3.4 Array variabler Länge' von programmersheaven.com/2/Pointers-and-Arrays-page-2
Arthur Ulfeldt
1
Dies unterscheidet sich jedoch nicht von der Behandlung mit Zeigern auf lokale Variablen. Sie können auch
herumgespielt werden
2
@ Jonathan Leffler Eine Sache, die Sie mit alloca machen können, aber nicht mit VLA, ist die Verwendung von Restrict Keyword mit ihnen. So: float * einschränken stark_used_arr = alloca (sizeof (float) * size); statt float stark_used_arr [Größe]. Es könnte einigen Compilern (in meinem Fall gcc 4.8) helfen, die Assembly zu optimieren, selbst wenn size eine Kompilierungskonstante ist. Siehe meine Frage dazu: stackoverflow.com/questions/19026643/using-restrict-with-arrays
Piotr Lopusiewicz
@JonathanLeffler Eine VLA ist lokal für den Block, der sie enthält. Ordnet andererseits alloca()Speicher zu, der bis zum Ende der Funktion dauert. Dies bedeutet, dass es anscheinend keine einfache und bequeme Übersetzung in VLA von gibt f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }. Wenn Sie der Meinung sind, dass es möglich ist, Verwendungen von allocain Verwendungen von VLA automatisch zu übersetzen, aber mehr als einen Kommentar benötigen, um zu beschreiben, wie, kann ich dies zu einer Frage machen.
Pascal Cuoq
40

Wie in diesem Newsgroup-Beitrag erwähnt , gibt es einige Gründe, warum die Verwendung allocaals schwierig und gefährlich angesehen werden kann:

  • Nicht alle Compiler unterstützen alloca.
  • Einige Compiler interpretieren das beabsichtigte Verhalten allocaunterschiedlich, sodass die Portabilität auch zwischen Compilern, die es unterstützen, nicht garantiert ist.
  • Einige Implementierungen sind fehlerhaft.
Freier Speicher
quelle
24
Eine Sache, die ich auf diesem Link erwähnt habe, der sich nicht an anderer Stelle auf dieser Seite befindet, ist, dass eine Funktion, die verwendet alloca(), separate Register zum Halten des Stapelzeigers und des Rahmenzeigers erfordert. Auf x86-CPUs> = 386 kann der Stapelzeiger ESPfür beide verwendet werden, um freizugeben EBP- sofern nicht alloca()verwendet.
j_random_hacker
10
Ein weiterer guter Punkt auf dieser Seite ist, dass, wenn der Codegenerator des Compilers dies nicht als Sonderfall behandelt, ein f(42, alloca(10), 43);Absturz auftreten kann, da möglicherweise der Stapelzeiger angepasst wird, alloca() nachdem mindestens eines der Argumente darauf gedrückt wurde.
j_random_hacker
3
Der verlinkte Beitrag scheint von John Levine geschrieben worden zu sein - dem Typen, der "Linkers and Loaders" geschrieben hat, ich würde vertrauen, was auch immer er sagt.
user318904
3
Der verlinkte Beitrag ist eine Antwort auf einen Beitrag von John Levine.
A. Wilcox
6
Denken Sie daran, dass sich seit 1991 viel geändert hat. Alle modernen C-Compiler (auch 2009) müssen Alloca als Sonderfall behandeln. Es ist eher eine intrinsische als eine gewöhnliche Funktion und ruft möglicherweise nicht einmal eine Funktion auf. Daher sollte das Problem der Allokation von Parametern (das in K & R C ab den 1970er Jahren auftrat) jetzt kein Problem mehr sein. Weitere Details in einem Kommentar, den ich zu Tony Ds Antwort abgegeben habe
Greggo
26

Ein Problem ist, dass es nicht Standard ist, obwohl es weitgehend unterstützt wird. Wenn andere Dinge gleich sind, würde ich immer eine Standardfunktion anstelle einer gemeinsamen Compiler-Erweiterung verwenden.

David Thornley
quelle
21

Trotzdem wird von der Verwendung von Allokationen abgeraten. Warum?

Ich sehe keinen solchen Konsens. Viele starke Profis; ein paar Nachteile:

  • C99 bietet Arrays variabler Länge, die häufig bevorzugt verwendet werden, da die Notation konsistenter mit Arrays fester Länge und insgesamt intuitiv ist
  • Viele Systeme verfügen über weniger Gesamtspeicher / Adressraum für den Stapel als für den Heap, was das Programm etwas anfälliger für Speicherauslastung (durch Stapelüberlauf) macht: Dies kann als eine gute oder eine schlechte Sache angesehen werden - eine Der Grund dafür, dass der Stack nicht automatisch so wächst wie der Heap, besteht darin, zu verhindern, dass außer Kontrolle geratene Programme die gesamte Maschine so stark beeinträchtigen
  • Bei Verwendung in einem lokaleren Bereich (z. B. einer whileoder einer forSchleife) oder in mehreren Bereichen sammelt sich der Speicher pro Iteration / Bereich an und wird erst freigegeben, wenn die Funktion beendet wird. Dies steht im Gegensatz zu normalen Variablen, die im Bereich einer Steuerungsstruktur definiert sind (z for {int i = 0; i < 2; ++i) { X }würde sich ansammelnalloca bei X angeforderten Speicher , aber der Speicher für ein Array mit fester Größe würde pro Iteration recycelt).
  • Moderne Compiler inlinefunktionieren normalerweise nicht mit aufrufenden Funktionen. allocaWenn Sie sie jedoch erzwingen, allocageschieht dies im Kontext des Aufrufers (dh der Stapel wird erst freigegeben, wenn der Aufrufer zurückkehrt).
  • Vor langer Zeit wurde allocavon einer nicht portablen Funktion / einem nicht portierbaren Hack auf eine standardisierte Erweiterung umgestellt, aber eine gewisse negative Wahrnehmung kann bestehen bleiben
  • Die Lebensdauer ist an den Funktionsumfang gebunden, der für den Programmierer möglicherweise besser geeignet ist als mallocdie explizite Steuerung
  • mallocWenn Sie diese verwenden müssen , müssen Sie über die Freigabe nachdenken. Wenn dies über eine Wrapper-Funktion (z. B. WonderfulObject_DestructorFree(ptr)) verwaltet wird, bietet die Funktion einen Punkt für Implementierungsbereinigungsvorgänge (wie das Schließen von Dateideskriptoren, das Freigeben interner Zeiger oder das Durchführen einer Protokollierung) ohne explizite Änderungen am Client Code: Manchmal ist es ein schönes Modell, konsequent zu übernehmen
    • In diesem Pseudo-OO-Programmierstil ist es natürlich, etwas zu wollen WonderfulObject* p = WonderfulObject_AllocConstructor();- das ist möglich, wenn der "Konstruktor" eine Funktion ist, mallocdie Speicher zurückgibt (da der Speicher zugewiesen bleibt, nachdem die Funktion den zu speichernden Wert zurückgegeben hat p), aber nicht wenn der "Konstruktor" verwendetalloca
      • Eine Makroversion von WonderfulObject_AllocConstructorkönnte dies erreichen, aber "Makros sind böse", da sie miteinander und mit Nicht-Makro-Code in Konflikt stehen und unbeabsichtigte Ersetzungen und daraus resultierende schwer zu diagnostizierende Probleme verursachen können
    • Fehlende freeVorgänge können von ValGrind, Purify usw. erkannt werden, aber fehlende "Destruktor" -Aufrufe können nicht immer erkannt werden - ein sehr schwacher Vorteil in Bezug auf die Durchsetzung der beabsichtigten Verwendung; Einige alloca()Implementierungen (z. B. GCCs) verwenden ein Inline-Makro für alloca(), sodass das Ersetzen einer Diagnosebibliothek zur Speichernutzung zur Laufzeit nicht so möglich ist wie für malloc/ realloc/ free(z. B. Elektrozaun).
  • Einige Implementierungen weisen subtile Probleme auf: Zum Beispiel von der Linux-Manpage:

    Auf vielen Systemen kann alloca () nicht in der Liste der Argumente eines Funktionsaufrufs verwendet werden, da der von alloca () reservierte Stapelspeicher auf dem Stapel in der Mitte des Bereichs für die Funktionsargumente angezeigt wird.


Ich weiß, dass diese Frage mit C gekennzeichnet ist, aber als C ++ - Programmierer dachte ich, ich würde C ++ verwenden, um den möglichen Nutzen von zu veranschaulichen alloca: Der folgende Code (und hier bei ideone ) erstellt einen Vektor, der polymorphe Typen unterschiedlicher Größe verfolgt, denen Stapel zugewiesen sind (mit Lebensdauer an Funktionsrückgabe gebunden) statt Heap zugewiesen.

#include <alloca.h>
#include <iostream>
#include <vector>

struct Base
{
    virtual ~Base() { }
    virtual int to_int() const = 0;
};

struct Integer : Base
{
    Integer(int n) : n_(n) { }
    int to_int() const { return n_; }
    int n_;
};

struct Double : Base
{
    Double(double n) : n_(n) { }
    int to_int() const { return -n_; }
    double n_;
};

inline Base* factory(double d) __attribute__((always_inline));

inline Base* factory(double d)
{
    if ((double)(int)d != d)
        return new (alloca(sizeof(Double))) Double(d);
    else
        return new (alloca(sizeof(Integer))) Integer(d);
}

int main()
{
    std::vector<Base*> numbers;
    numbers.push_back(factory(29.3));
    numbers.push_back(factory(29));
    numbers.push_back(factory(7.1));
    numbers.push_back(factory(2));
    numbers.push_back(factory(231.0));
    for (std::vector<Base*>::const_iterator i = numbers.begin();
         i != numbers.end(); ++i)
    {
        std::cout << *i << ' ' << (*i)->to_int() << '\n';
        (*i)->~Base();   // optionally / else Undefined Behaviour iff the
                         // program depends on side effects of destructor
    }
}
Tony Delroy
quelle
nein +1 wegen der fischartigen, eigenwilligen Art, mit verschiedenen Typen umzugehen :-(
einpoklum
@einpoklum: Nun, das ist zutiefst aufschlussreich ... danke.
Tony Delroy
1
Lassen Sie mich umformulieren: Dies ist eine sehr gute Antwort. Bis zu dem Punkt, an dem Sie meiner Meinung nach vorschlagen, dass die Leute eine Art Gegenmuster verwenden.
Einpoklum
Der Kommentar von der Linux-Manpage ist sehr alt und, da bin ich mir ziemlich sicher, veraltet. Alle modernen Compiler wissen, was alloca () ist, und stolpern nicht so über ihre Schnürsenkel. Im alten K & R C (1) verwendeten alle Funktionen Rahmenzeiger. (2) Alle Funktionsaufrufe waren {Push-Argumente auf Stapel} {Aufruffunktion} {Add # n, sp}. alloca war eine lib-Funktion, die den Stack nur anstoßen würde, der Compiler wusste nicht einmal, dass dies passiert. (1) und (2) sind nicht mehr wahr, so dass Alloca nicht so funktionieren kann (jetzt ist es eine intrinsische). Im alten C würde das Aufrufen von Alloca während des Drückens von Argumenten offensichtlich auch diese Annahmen brechen.
Greggo
4
In Bezug auf das Beispiel wäre ich im Allgemeinen besorgt über etwas, das always_inline erfordert , um Speicherbeschädigungen zu vermeiden ....
Greggo
14

Alle anderen Antworten sind richtig. Wenn das, was Sie verwenden möchten, alloca()jedoch relativ klein ist, denke ich, dass es eine gute Technik ist, die schneller und bequemer ist als die Verwendung malloc()oder auf andere Weise.

Mit anderen Worten, alloca( 0x00ffffff )ist gefährlich und verursacht wahrscheinlich genau so viel Überlauf wie es char hugeArray[ 0x00ffffff ];ist. Seien Sie vorsichtig und vernünftig und es wird Ihnen gut gehen.

JSB ձոգչ
quelle
12

Viele interessante Antworten auf diese "alte" Frage, sogar einige relativ neue Antworten, aber ich habe keine gefunden, die dies erwähnen ...

Bei sachgemäßer und sorgfältiger Verwendung kann die konsequente Verwendung alloca() (möglicherweise anwendungsweit) zur Verarbeitung kleiner Zuordnungen variabler Länge (oder C99-VLAs, sofern verfügbar) zu einem geringeren Gesamtstapelwachstum führen als eine ansonsten gleichwertige Implementierung mit übergroßen lokalen Arrays fester Länge . So alloca()kann es sich gut für die Stapel , wenn Sie es sorgfältig verwenden.

Ich fand dieses Zitat in ... OK, ich habe dieses Zitat erfunden. Aber wirklich, denk darüber nach ...

@j_random_hacker ist in seinen Kommentaren unter anderen Antworten sehr richtig: Wenn Sie die Verwendung von alloca()übergroßen lokalen Arrays vermeiden, wird Ihr Programm nicht vor Stapelüberläufen sicherer (es sei denn, Ihr Compiler ist alt genug, um das Inlining von Funktionen zu ermöglichen, die alloca()in diesem Fall verwendet werden sollten Upgrade oder es sei denn, Sie verwenden alloca()Inside-Loops. In diesem Fall sollten Sie ... keine alloca()Inside-Loops verwenden.

Ich habe an Desktop / Server-Umgebungen und eingebetteten Systemen gearbeitet. Viele eingebettete Systeme verwenden überhaupt keinen Heap (sie verknüpfen ihn nicht einmal), aus Gründen, die die Wahrnehmung einschließen, dass dynamisch zugewiesener Speicher aufgrund des Risikos von Speicherverlusten in einer Anwendung, die niemals funktioniert, böse ist Neustarts über Jahre hinweg oder die vernünftigere Rechtfertigung, dass dynamischer Speicher gefährlich ist, da nicht sicher bekannt sein kann, dass eine Anwendung ihren Heap niemals bis zur Erschöpfung des falschen Speichers fragmentieren wird. Eingebettete Programmierer haben also nur wenige Alternativen.

alloca() (oder VLAs) sind möglicherweise genau das richtige Werkzeug für den Job.

Ich habe immer wieder gesehen, wie ein Programmierer einen vom Stapel zugewiesenen Puffer "groß genug macht, um jeden möglichen Fall zu behandeln". In einem tief verschachtelten Aufrufbaum führt die wiederholte Verwendung dieses (Anti -?) Musters zu einer übertriebenen Stapelverwendung. (Stellen Sie sich einen Anrufbaum mit einer Tiefe von 20 Ebenen vor, bei dem die Funktion auf jeder Ebene aus unterschiedlichen Gründen blind einen Puffer von 1024 Bytes "nur um sicher zu gehen" zu viel zuweist, wenn im Allgemeinen nur 16 oder weniger davon verwendet werden, und dies nur in sehr In seltenen Fällen kann mehr verwendet werden.) Eine Alternative ist die Verwendungalloca()oder VLAs und weisen nur so viel Stapelspeicher zu, wie Ihre Funktion benötigt, um eine unnötige Belastung des Stapels zu vermeiden. Wenn eine Funktion im Aufrufbaum eine überdurchschnittliche Zuweisung benötigt, verwenden andere im Aufrufbaum hoffentlich immer noch ihre normalen kleinen Zuordnungen, und die Gesamtnutzung des Anwendungsstapels ist erheblich geringer, als wenn jede Funktion einen lokalen Puffer blind überbelegt hätte .

Aber wenn Sie sich entscheiden zu verwenden alloca()...

Basierend auf anderen Antworten auf dieser Seite scheint es, dass VLAs sicher sein sollten (sie setzen keine Stapelzuweisungen zusammen, wenn sie aus einer Schleife heraus aufgerufen werden), aber wenn Sie sie verwenden alloca(), achten Sie darauf , sie nicht innerhalb einer Schleife zu verwenden, und machen Sie Stellen Sie sicher, dass Ihre Funktion nicht eingebunden werden kann, wenn die Möglichkeit besteht, dass sie in der Schleife einer anderen Funktion aufgerufen wird.

Phonetagger
quelle
Ich stimme diesem Punkt zu. Das Gefährliche von alloca()ist wahr, aber das Gleiche gilt für Speicherlecks mit malloc()(warum dann nicht einen GC verwenden? Man könnte argumentieren). alloca()Bei sorgfältiger Verwendung kann dies sehr nützlich sein, um die Stapelgröße zu verringern.
Felipe Tonello
Ein weiterer guter Grund, keinen dynamischen Speicher zu verwenden, insbesondere in Embedded: Es ist komplizierter als das Festhalten am Stapel. Die Verwendung des dynamischen Speichers erfordert spezielle Prozeduren und Datenstrukturen, während es auf dem Stapel (zur Vereinfachung) darum geht, eine höhere Zahl vom Stapelzeiger zu addieren / zu subtrahieren.
Tehftw
Nebenbemerkung: Das Beispiel "Verwenden eines festen Puffers [MAX_SIZE]" zeigt, warum die Richtlinie für überlastetes Speichern so gut funktioniert. Programme weisen Speicher zu, den sie möglicherweise nur an den Grenzen ihrer Pufferlänge berühren. Es ist also in Ordnung, dass Linux (und andere Betriebssysteme) erst bei der ersten Verwendung eine Speicherseite zuweisen (im Gegensatz zu malloc'd). Wenn der Puffer größer als eine Seite ist, verwendet das Programm möglicherweise nur die erste Seite und verschwendet nicht den Rest des physischen Speichers.
Katastic Voyage
@KatasticVoyage Sofern MAX_SIZE nicht größer (oder mindestens gleich) der Größe der virtuellen Speicherseite Ihres Systems ist, enthält Ihr Argument kein Wasser. Auch auf eingebetteten Systemen ohne virtuellen Speicher (viele eingebettete MCUs haben keine MMUs) kann die Richtlinie für überlasteten Speicher unter dem Gesichtspunkt "Sicherstellen, dass Ihr Programm in allen Situationen ausgeführt wird" gut sein, aber diese Gewissheit hängt mit dem Preis Ihrer Stapelgröße zusammen muss ebenfalls zugewiesen werden, um diese Richtlinie für überlasteten Speicher zu unterstützen. Bei einigen eingebetteten Systemen ist dies ein Preis, den einige Hersteller von kostengünstigen Produkten nicht bereit sind zu zahlen.
Phonetagger
11

Jeder hat bereits auf die große Sache hingewiesen, die ein potenzielles undefiniertes Verhalten aufgrund eines Stapelüberlaufs ist, aber ich sollte erwähnen, dass die Windows-Umgebung einen großartigen Mechanismus hat, um dies mithilfe von strukturierten Ausnahmen (SEH) und Schutzseiten abzufangen. Da der Stapel nur nach Bedarf wächst, befinden sich diese Schutzseiten in Bereichen, die nicht zugeordnet sind. Wenn Sie ihnen zuweisen (indem Sie den Stapel überlaufen), wird eine Ausnahme ausgelöst.

Sie können diese SEH-Ausnahme abfangen und _resetstkoflw aufrufen, um den Stapel zurückzusetzen und Ihren fröhlichen Weg fortzusetzen. Es ist nicht ideal, aber es ist ein weiterer Mechanismus, um zumindest zu wissen, dass etwas schief gelaufen ist, wenn das Zeug den Fan trifft. * nix könnte etwas Ähnliches haben, das mir nicht bekannt ist.

Ich empfehle, Ihre maximale Zuordnungsgröße zu begrenzen, indem Sie die Zuordnung einwickeln und intern verfolgen. Wenn Sie wirklich hart im Nehmen sind, können Sie einige Bereichswachen an die Spitze Ihrer Funktion setzen, um alle Zuordnungszuordnungen im Funktionsumfang zu verfolgen, und überprüfen Sie dies anhand des für Ihr Projekt maximal zulässigen Betrags.

Zusätzlich dazu, dass Speicherlecks nicht berücksichtigt werden, verursacht die Zuweisung keine Speicherfragmentierung, was ziemlich wichtig ist. Ich denke nicht, dass Alloca eine schlechte Praxis ist, wenn Sie es intelligent verwenden, was im Grunde für alles gilt. :-)

SilentDirge
quelle
Das Problem ist, dass alloca()so viel Platz benötigt werden kann, dass der Stapelzeiger auf dem Haufen landet. Damit kann ein Angreifer, der die Größe alloca()und die Daten, die in diesen Puffer gelangen , steuern kann, den Heap überschreiben (was sehr schlecht ist).
12431234123412341234123
SEH ist eine reine Windows-Sache. Das ist großartig, wenn Sie sich nur um Ihren Code kümmern, der unter Windows ausgeführt wird, aber wenn Ihr Code plattformübergreifend sein muss (oder wenn Sie Code schreiben, der nur auf einer Nicht-Windows-Plattform ausgeführt wird), können Sie sich nicht darauf verlassen SEH.
George
10

alloca () ist nett und effizient ... aber es ist auch tief gebrochen.

  • defektes Bereichsverhalten (Funktionsbereich anstelle von Blockbereich)
  • Verwenden Sie inkonsistent mit malloc ( alloca () -ted Zeiger sollte nicht freigegeben werden, von nun an müssen Sie verfolgen, woher Ihre Zeiger kommen, um free () nur diejenigen, die Sie mit malloc () erhalten haben )
  • schlechtes Verhalten, wenn Sie auch Inlining verwenden (der Bereich geht manchmal an die Anruferfunktion, je nachdem, ob der Angerufene inliniert ist oder nicht).
  • Keine Überprüfung der Stapelgrenzen
  • undefiniertes Verhalten im Fehlerfall (gibt nicht NULL wie malloc zurück ... und was bedeutet Fehler, da die Stapelgrenzen sowieso nicht überprüft werden ...)
  • nicht ansi standard

In den meisten Fällen können Sie es durch lokale Variablen und Hauptgröße ersetzen. Wenn es für große Objekte verwendet wird, ist es normalerweise sicherer, sie auf den Haufen zu legen.

Wenn Sie es wirklich brauchen, können Sie VLA verwenden (kein Vla in C ++, schade). Sie sind in Bezug auf Umfangsverhalten und -konsistenz viel besser als alloca (). Aus meiner Sicht sind VLA eine Art Allokation () die richtig gemacht wurde.

Natürlich ist eine lokale Struktur oder ein lokales Array, das einen Großteil des benötigten Speicherplatzes verwendet, immer noch besser, und wenn Sie nicht über eine so große Heap-Zuordnung mit normalem malloc () verfügen, ist dies wahrscheinlich sinnvoll. Ich sehe keinen vernünftigen Anwendungsfall, in dem Sie wirklich entweder alloca () oder VLA brauchen .

kriss
quelle
Ich sehe den Grund für die Ablehnung nicht (übrigens ohne Kommentar)
gd1
Nur Namen haben Geltungsbereich. allocaerstellt keinen Namen, sondern nur einen Speicherbereich, der eine Lebensdauer hat .
Neugieriger
@curiousguy: du spielst nur mit worten. Bei automatischen Variablen könnte ich auch von der Lebensdauer des zugrunde liegenden Speichers sprechen, da diese dem Umfang des Namens entspricht. Wie auch immer, das Problem ist nicht, wie wir es nennen, sondern die Instabilität der Lebensdauer / des Umfangs des Gedächtnisses, die durch die Allokation zurückgegeben wird, und das außergewöhnliche Verhalten.
kriss
2
Ich wünschte, Alloca hätte eine entsprechende "Freea" gehabt, mit der Spezifikation, dass das Aufrufen von "Freea" die Auswirkungen der "Alloca", die das Objekt erstellt hat, und aller nachfolgenden Objekte rückgängig machen würde, und eine Anforderung, dass die Speicherung innerhalb einer Funktion "zugeteilt" werden muss sei auch darin "frei". Dies hätte es nahezu allen Implementierungen ermöglicht, alloca / freea auf kompatible Weise zu unterstützen, die Inlining-Probleme zu lösen und die Dinge im Allgemeinen viel sauberer zu machen.
Supercat
2
@supercat - das wünsche ich auch. Aus diesem Grunde (und mehr), verwende ich eine Abstraktionsschicht (meist Makros und Inline - Funktionen) , so dass ich überhaupt nicht anrufen allocaoder mallocoder freedirekt. Ich sage Dinge wie {stack|heap}_alloc_{bytes,items,struct,varstruct}und {stack|heap}_dealloc. Also, heap_deallocruft einfach an freeund stack_deallocist ein No-Op. Auf diese Weise können die Stapelzuweisungen leicht auf Heapzuweisungen herabgestuft werden, und die Absichten sind auch klarer.
Todd Lehman
9

Hier ist der Grund:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

Nicht, dass irgendjemand diesen Code schreiben würde, aber das Größenargument, an das Sie übergeben, allocastammt mit ziemlicher Sicherheit von einer Art Eingabe, die böswillig darauf abzielen könnte, Ihr Programm auf so allocaetwas Großes zu bringen. Wenn die Größe nicht auf Eingaben basiert oder nicht groß sein kann, warum haben Sie dann nicht einfach einen kleinen lokalen Puffer mit fester Größe deklariert?

Praktisch jeder Code, der allocaund / oder C99 vlas verwendet, weist schwerwiegende Fehler auf, die zu Abstürzen (wenn Sie Glück haben) oder Kompromissen bei Privilegien (wenn Sie nicht so viel Glück haben) führen.

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
1
Die Welt wird es vielleicht nie erfahren. :( Das heißt, ich hoffe, Sie könnten eine Frage klären, die ich habe alloca. Sie sagten, dass fast jeder Code, der ihn verwendet, einen Fehler aufweist, aber ich hatte vor, ihn zu verwenden. Normalerweise würde ich eine solche Behauptung ignorieren, aber kommen Ich schreibe eine virtuelle Maschine und möchte Variablen zuweisen, die aufgrund der enormen Beschleunigung nicht dynamisch, sondern dynamisch aus der Funktion auf dem Stapel entkommen. Gibt es eine Alternative? Ich weiß, dass ich mit Speicherpools in die Nähe kommen kann, aber das ist immer noch nicht so billig. Was würden Sie tun?
GManNickG
7
Wissen Sie, was auch gefährlich ist? Das: *0 = 9;ERSTAUNLICH !!! Ich denke, ich sollte niemals Zeiger verwenden (oder sie zumindest dereferenzieren). Ähm, warte. Ich kann testen, ob es null ist. Hmm. Ich denke, ich kann auch die Größe des Speichers testen, über den ich zuweisen möchte alloca. Seltsamer Mann. Seltsam.
Thomas Eding
7
*0=9;ist nicht gültig C. Um die Größe zu testen, an die Sie übergeben alloca, testen Sie sie gegen was? Es gibt keine Möglichkeit, das Limit zu kennen, und wenn Sie es nur mit einer winzigen festen bekannten sicheren Größe (z. B. 8 KB) testen möchten, können Sie auch einfach ein Array mit fester Größe auf dem Stapel verwenden.
R .. GitHub STOP HELPING ICE
7
Das Problem mit Ihrem Argument "Entweder ist die Größe als klein genug bekannt oder es ist eingabeabhängig und kann daher beliebig groß sein", wie ich sehe, ist, dass es genauso stark für die Rekursion gilt. Ein praktischer Kompromiss (für beide Fälle) besteht darin, anzunehmen, dass small_constant * log(user_input)wir wahrscheinlich über genügend Speicher verfügen , wenn die Größe bis dahin begrenzt ist .
j_random_hacker
1
In der Tat haben Sie den EINEN Fall identifiziert, in dem VLA / Alloca nützlich ist: rekursive Algorithmen, bei denen der maximal benötigte Speicherplatz in einem Aufrufrahmen so groß wie N sein kann, die Summe des auf allen Rekursionsebenen benötigten Speicherplatzes jedoch N oder eine Funktion ist von N wächst das nicht schnell.
R .. GitHub STOP HELPING ICE
9

Ich glaube nicht, dass dies jemand erwähnt hat: Die Verwendung von Alloca in einer Funktion behindert oder deaktiviert einige Optimierungen, die andernfalls in der Funktion angewendet werden könnten, da der Compiler die Größe des Stapelrahmens der Funktion nicht kennen kann.

Eine übliche Optimierung durch C-Compiler besteht beispielsweise darin, die Verwendung des Rahmenzeigers innerhalb einer Funktion zu eliminieren. Stattdessen werden Rahmenzugriffe relativ zum Stapelzeiger vorgenommen. Es gibt also noch ein Register für den allgemeinen Gebrauch. Wenn jedoch innerhalb der Funktion eine Zuordnung aufgerufen wird, ist der Unterschied zwischen sp und fp für einen Teil der Funktion unbekannt, sodass diese Optimierung nicht durchgeführt werden kann.

Angesichts der Seltenheit seiner Verwendung und seines zwielichtigen Status als Standardfunktion deaktivieren Compiler-Designer möglicherweise jede Optimierung, die Probleme mit der Allokation verursachen könnte, wenn mehr als ein wenig Aufwand erforderlich wäre, damit sie mit der Allokation funktioniert.

UPDATE: Da lokale Arrays mit variabler Länge zu C hinzugefügt wurden und diese dem Compiler sehr ähnliche Probleme bei der Codegenerierung wie alloca stellen, sehe ich, dass "Seltenheit der Verwendung und schattiger Status" nicht für den zugrunde liegenden Mechanismus gilt. Ich würde jedoch immer noch vermuten, dass die Verwendung von Alloca oder VLA die Codegenerierung innerhalb einer Funktion, die sie verwendet, beeinträchtigt. Ich würde mich über Feedback von Compiler-Designern freuen.

Greggo
quelle
1
Arrays mit variabler Länge wurden C ++ nie hinzugefügt.
Nir Friedman
@NirFriedman In der Tat. Ich denke, es gab eine Wikipedia-Feature-Liste, die auf einem alten Vorschlag basierte.
Greggo
> Ich würde immer noch vermuten, dass die Verwendung von Alloca oder VLA die Codegenerierung beeinträchtigt. Ich würde denken, dass die Verwendung von Alloca einen Frame-Zeiger erfordert, da sich der Stack-Zeiger auf eine Weise bewegt, die zum Zeitpunkt der Kompilierung nicht offensichtlich ist. alloca kann in einer Schleife aufgerufen werden, um immer mehr Stapelspeicher zu erhalten, oder mit einer zur Laufzeit berechneten Größe usw. Wenn ein Frame-Zeiger vorhanden ist, hat der generierte Code einen stabilen Verweis auf die Einheimischen und der Stack-Zeiger kann tun, was er will. es wird nicht verwendet.
Kaz vor
8

Eine Gefahr allocabesteht darin, dass longjmpes zurückgespult wird.

Das heißt, wenn Sie einen Kontext mit setjmp, dann allocaetwas Speicher, dann longjmpim Kontext speichern, können Sie den allocaSpeicher verlieren . Der Stapelzeiger befindet sich wieder dort, wo er war, und der Speicher ist nicht mehr reserviert. Wenn Sie eine Funktion aufrufen oder eine andere allocaausführen, wird das Original beschädigtalloca .

Um dies zu verdeutlichen, beziehe ich mich hier ausdrücklich auf eine Situation longjmp, in der die Funktion, in der allocadie stattgefunden hat, nicht verlassen wird! Vielmehr speichert eine Funktion den Kontext mit setjmp; ordnet dann Speicher zu allocaund schließlich findet ein longjmp zu diesem Kontext statt. Der allocaSpeicher dieser Funktion wird nicht vollständig freigegeben. nur der gesamte Speicher, den es seit dem zugewiesen hat setjmp. Natürlich spreche ich von einem beobachteten Verhalten; Von denen alloca, die ich kenne, ist keine solche Anforderung dokumentiert .

Der Fokus in der Dokumentation liegt normalerweise auf dem Konzept, dass der allocaSpeicher einer Funktionsaktivierung zugeordnet ist , nicht einem Block. dass mehrere Aufrufe von allocanur mehr Stapelspeicher greifen, die alle freigegeben werden, wenn die Funktion beendet wird. Nicht so; Der Speicher ist tatsächlich dem Prozedurkontext zugeordnet. Wenn der Kontext mit wiederhergestellt wird longjmp, ist dies auch der vorherige allocaStatus. Dies ist eine Folge davon, dass das Stapelzeigerregister selbst für die Zuordnung verwendet und (notwendigerweise) im Speicher gespeichert und wiederhergestellt wird jmp_buf.

Im Übrigen bietet dies, wenn es so funktioniert, einen plausiblen Mechanismus, um den zugewiesenen Speicher absichtlich freizugeben alloca.

Ich bin darauf als Grundursache eines Fehlers gestoßen.

Kaz
quelle
1
Das ist es, was es tun soll - longjmpgeht zurück und macht es so, dass das Programm alles vergisst, was im Stapel passiert ist: alle Variablen, Funktionsaufrufe usw. Und es allocaist wie ein Array auf dem Stapel, also wird erwartet, dass sie überlastet werden wie alles andere auf dem Stapel.
Der
1
man allocagab den folgenden Satz aus: "Da der von alloca () zugewiesene Speicherplatz innerhalb des Stapelrahmens zugewiesen wird, wird dieser Speicherplatz automatisch freigegeben, wenn die Funktionsrückgabe durch einen Aufruf von longjmp (3) oder siglongjmp (3) übersprungen wird." Es ist also dokumentiert, dass der mit zugewiesene allocaSpeicher nach a überlastet wird longjmp.
Der
@tehftw Die beschriebene Situation tritt auf, ohne dass eine Funktionsrückgabe übersprungen wird longjmp. Die Zielfunktion ist noch nicht zurückgekehrt. Es wird getan setjmp, allocaund dann longjmp. Sie longjmpkönnen den allocaZustand auf das zurückspulen, was er zu der setjmpZeit war. Das heißt, der von bewegte Zeiger hat allocadas gleiche Problem wie eine lokale Variable, die nicht markiert wurde volatile!
Kaz
3
Ich verstehe nicht, warum es unerwartet sein sollte. Wenn Sie setjmpdann allocaund dann longjmpist es normal, allocadass zurückgespult wird. Der springende Punkt longjmpist, zu dem Zustand zurückzukehren, in dem gerettet wurde setjmp!
Der
@tehftw Ich habe diese spezielle Interaktion noch nie dokumentiert gesehen. Daher kann man sich auf keinen anderen Weg verlassen, als durch empirische Untersuchungen mit Compilern.
Kaz
7

Ein Ort, an dem alloca()es besonders gefährlich ist als malloc()der Kernel - der Kernel eines typischen Betriebssystems hat einen Stapelspeicher mit fester Größe, der in einem seiner Header fest codiert ist. Es ist nicht so flexibel wie der Stapel einer Anwendung. Wenn Sie einen Anruf alloca()mit einer ungerechtfertigten Größe tätigen, kann der Kernel abstürzen. Bestimmte Compiler warnen vor der Verwendung von alloca()(und sogar VLAs) unter bestimmten Optionen, die beim Kompilieren eines Kernel-Codes aktiviert werden sollten. Hier ist es besser, Speicher im Heap zuzuweisen, der nicht durch ein fest codiertes Limit festgelegt ist.

Sondhi Chakraborty
quelle
7
alloca()ist nicht gefährlicher als in int foo[bar];dem bareine willkürliche ganze Zahl ist.
Todd Lehman
@ToddLehman Das stimmt, und genau aus diesem Grund haben wir VLAs im Kernel seit mehreren Jahren verboten und sind seit 2018 VLA-frei :-)
Chris Down
6

Wenn Sie versehentlich über den mit zugewiesenen Block hinaus schreiben alloca(z. B. aufgrund eines Pufferüberlaufs), überschreiben Sie die Rücksprungadresse Ihrer Funktion, da sich diese "über" dem Stapel befindet, dh nach Ihrem zugewiesenen Block.

_alloca Block auf dem Stapel

Dies hat zwei Konsequenzen:

  1. Das Programm stürzt spektakulär ab und es ist unmöglich zu sagen, warum oder wo es abgestürzt ist (der Stapel wird aufgrund des überschriebenen Frame-Zeigers höchstwahrscheinlich zu einer zufälligen Adresse abgewickelt).

  2. Dies macht den Pufferüberlauf um ein Vielfaches gefährlicher, da ein böswilliger Benutzer eine spezielle Nutzlast erstellen kann, die auf den Stapel gelegt wird und daher ausgeführt werden kann.

Wenn Sie dagegen über einen Block auf dem Heap hinaus schreiben, erhalten Sie "nur" eine Heap-Beschädigung. Das Programm wird wahrscheinlich unerwartet beendet, aber den Stapel ordnungsgemäß abwickeln, wodurch die Wahrscheinlichkeit einer Ausführung von bösartigem Code verringert wird.

Rustyx
quelle
11
Nichts in dieser Situation unterscheidet sich dramatisch von den Gefahren des Pufferüberlaufs eines Stapels mit fester Größe und Stapelzuweisung. Diese Gefahr ist nicht nur auf alloca.
Phonetagger
2
Natürlich nicht. Aber bitte überprüfen Sie die ursprüngliche Frage. Die Frage ist: Was ist die Gefahr allocaim Vergleich zu malloc(also kein Puffer mit fester Größe auf dem Stapel).
Rustyx
Kleiner Punkt, aber Stapel auf einigen Systemen wachsen nach oben (z. B. PIC-16-Bit-Mikroprozessoren).
EBlake
5

Leider alloca()fehlt das wirklich großartige im fast fantastischen tcc. Gcc hat alloca().

  1. Es sät den Samen seiner eigenen Zerstörung. Mit Rückkehr als Destruktor.

  2. Als malloc()ob es einen ungültigen Zeiger auf Fail zurückgibt, der auf modernen Systemen mit einer MMU fehlerhaft ist (und hoffentlich diejenigen ohne neu startet).

  3. Im Gegensatz zu automatischen Variablen können Sie die Größe zur Laufzeit angeben.

Es funktioniert gut mit Rekursion. Sie können statische Variablen verwenden, um etwas Ähnliches wie die Schwanzrekursion zu erreichen, und nur wenige andere verwenden, um Informationen an jede Iteration zu übergeben.

Wenn Sie zu tief drücken, ist Ihnen ein Segfault sicher (wenn Sie eine MMU haben).

Beachten Sie, dass dies malloc()nicht mehr bietet, da NULL zurückgegeben wird (was bei Zuweisung ebenfalls zu einem Segfault führt), wenn das System nicht über genügend Speicher verfügt. Das heißt, Sie können nur eine Kaution hinterlegen oder einfach versuchen, sie auf irgendeine Weise zuzuweisen.

Zur Verwendung verwende malloc()ich Globals und weise sie NULL zu. Wenn der Zeiger nicht NULL ist, gebe ich ihn frei, bevor ich ihn benutze malloc().

Sie können auch realloc()als allgemeinen Fall verwenden, wenn Sie vorhandene Daten kopieren möchten. Sie müssen den Zeiger vorher überprüfen, um herauszufinden, ob Sie nach dem kopieren oder verketten möchten realloc().

3.2.5.2 Vorteile der Zuteilung

Zagam
quelle
4
Tatsächlich sagt die Alloca-Spezifikation nicht, dass sie bei einem Fehler einen ungültigen Zeiger zurückgibt (Stapelüberlauf), dass sie ein undefiniertes Verhalten aufweist ... und für malloc heißt es, dass sie NULL zurückgibt, keinen zufälligen ungültigen Zeiger (OK, die optimistische Linux-Speicherimplementierung macht das nutzlos).
kriss
@kriss Linux kann Ihren Prozess beenden, aber es
wagt sich
@ craig65535: Der Ausdruck undefiniertes Verhalten bedeutet normalerweise, dass dieses Verhalten nicht durch die C- oder C ++ - Spezifikation definiert ist. Nicht in irgendeiner Weise, dass es auf einem bestimmten Betriebssystem oder Compiler zufällig oder instabil ist. Daher ist es sinnlos, UB mit dem Namen eines Betriebssystems wie "Linux" oder "Windows" zu verknüpfen. Das hat damit nichts zu tun.
Kriss
Ich habe versucht zu sagen, dass malloc, das NULL zurückgibt, oder im Fall von Linux ein Speicherzugriff, der Ihren Prozess beendet, dem undefinierten Verhalten von alloca vorzuziehen ist. Ich denke, ich muss Ihren ersten Kommentar falsch verstanden haben.
craig65535
3

Für Prozesse steht nur eine begrenzte Menge an Stapelspeicherplatz zur Verfügung - weit weniger als der verfügbare Speicherplatz malloc().

Durch alloca()Ihre Verwendung erhöhen Sie die Wahrscheinlichkeit eines Stapelüberlauffehlers erheblich (wenn Sie Glück haben, oder eines unerklärlichen Absturzes, wenn Sie dies nicht tun).

RichieHindle
quelle
Das hängt sehr stark von der Anwendung ab. Es ist nicht ungewöhnlich, dass eine speicherbeschränkte eingebettete Anwendung eine Stapelgröße aufweist, die größer als der Heap ist (wenn es überhaupt einen Heap gibt).
EBlake
3

Nicht sehr hübsch, aber wenn die Leistung wirklich wichtig ist, können Sie Speicherplatz auf dem Stapel vorab zuweisen.

Wenn Sie bereits jetzt die maximale Größe des Speicherblocks haben, den Sie benötigen, und Überlaufprüfungen durchführen möchten, können Sie Folgendes tun:

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}
Sylvain Rodrigue
quelle
12
Ist das Char-Array für jeden Datentyp garantiert korrekt ausgerichtet? Alloca bietet ein solches Versprechen.
Juho Östman
@ JuhoÖstman: Sie können ein Array von struct (oder einem beliebigen Typ) anstelle von char verwenden, wenn Sie Ausrichtungsprobleme haben.
kriss
Das nennt man ein Array mit variabler Länge . Es wird in C90 und höher unterstützt, jedoch nicht in C ++. Siehe Kann ich ein C-Array mit variabler Länge in C ++ 03 und C ++ 11 verwenden?
JWW
3

Die Allokationsfunktion ist großartig und alle Neinsager verbreiten einfach FUD.

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

Array und Parray sind genau gleich mit genau den gleichen Risiken. Zu sagen, einer sei besser als der andere, ist eine syntaktische und keine technische Wahl.

Bei der Auswahl von Stapelvariablen im Vergleich zu Heap-Variablen bieten lang laufende Programme, die Stack-over-Heap für Variablen mit In-Scope-Lebensdauer verwenden, viele Vorteile. Sie vermeiden eine Heap-Fragmentierung und Sie können vermeiden, Ihren Prozessspeicher mit nicht verwendetem (unbrauchbarem) Heap-Speicherplatz zu vergrößern. Sie müssen es nicht aufräumen. Sie können die Stapelzuordnung im Prozess steuern.

Warum ist das so schlimm?

mlwmohawk
quelle
3

Tatsächlich ist nicht garantiert, dass Alloca den Stapel verwendet. In der Tat reserviert die gcc-2.95-Implementierung von alloca Speicher vom Heap unter Verwendung von malloc selbst. Auch diese Implementierung ist fehlerhaft. Sie kann zu einem Speicherverlust und zu unerwartetem Verhalten führen, wenn Sie sie innerhalb eines Blocks mit einer weiteren Verwendung von goto aufrufen. Nicht, um zu sagen, dass Sie es niemals verwenden sollten, aber manchmal führt Allokation zu mehr Overhead, als es von Ihnen löst.

user7491277
quelle
Es hört sich so an, als ob gcc-2.95 die Zuordnung gebrochen hat und wahrscheinlich nicht sicher für Programme verwendet werden kann, die dies erfordern alloca. Wie hätte es den Speicher bereinigt, wenn longjmpes verwendet wird, um Frames aufzugeben, die dies getan haben alloca? Wann würde jemand heute gcc 2.95 verwenden?
Kaz
2

IMHO wird Alloca als schlechte Praxis angesehen, da jeder Angst hat, die Stapelgrößenbeschränkung zu erschöpfen.

Ich habe viel gelernt, indem ich diesen Thread und einige andere Links gelesen habe:

Ich verwende alloca hauptsächlich, um meine einfachen C-Dateien auf msvc und gcc ohne Änderung, C89-Stil, ohne #ifdef _MSC_VER usw. kompilierbar zu machen.

Vielen Dank ! Dieser Thread hat mich dazu gebracht, mich auf dieser Seite anzumelden :)

ytoto
quelle
Denken Sie daran, dass es auf dieser Site keinen "Thread" gibt. Der Stapelüberlauf hat ein Frage-Antwort-Format, kein Diskussionsthread-Format. "Antwort" ist nicht wie "Antworten" in einem Diskussionsforum. Dies bedeutet, dass Sie tatsächlich eine Antwort auf die Frage geben und nicht dazu verwendet werden sollten, auf andere Antworten oder Kommentare zum Thema zu antworten. Sobald Sie mindestens 50 Wiederholungen haben, können Sie Kommentare posten , aber lesen Sie unbedingt "Wann sollte ich nicht kommentieren?" Sektion. Bitte lesen Sie die Info- Seite, um das Format der Site besser zu verstehen.
Adi Inbar
1

Meiner Meinung nach sollte alloca (), sofern verfügbar, nur eingeschränkt verwendet werden. Ähnlich wie bei der Verwendung von "goto" hat eine große Anzahl ansonsten vernünftiger Leute eine starke Abneigung nicht nur gegen die Verwendung von, sondern auch gegen die Existenz von alloca ().

Für die eingebettete Verwendung, bei der die Stapelgröße bekannt ist und durch Konvention und Analyse Grenzen für die Größe der Zuordnung festgelegt werden können und bei der der Compiler nicht zur Unterstützung von C99 + aktualisiert werden kann, ist die Verwendung von alloca () in Ordnung, und das war ich auch bekannt, um es zu verwenden.

Wenn verfügbar, können VLAs einige Vorteile gegenüber alloca () haben: Der Compiler kann Stapelbegrenzungsprüfungen generieren, die den Zugriff außerhalb der Grenzen abfangen, wenn der Zugriff im Array-Stil verwendet wird (ich weiß nicht, ob Compiler dies tun, aber es kann durchgeführt werden), und die Analyse des Codes kann bestimmen, ob die Array-Zugriffsausdrücke ordnungsgemäß begrenzt sind. Beachten Sie, dass in einigen Programmierumgebungen, z. B. in der Automobilindustrie, in medizinischen Geräten und in der Avionik, diese Analyse auch für Arrays mit fester Größe durchgeführt werden muss, sowohl automatisch (auf dem Stapel) als auch statisch (global oder lokal).

Auf Architekturen, die sowohl Daten als auch Rücksprungadressen / Rahmenzeiger auf dem Stapel speichern (soweit ich weiß, sind das alle), kann jede dem Stapel zugewiesene Variable gefährlich sein, da die Adresse der Variablen verwendet werden kann und ungeprüfte Eingabewerte dies möglicherweise zulassen alle Arten von Unheil.

Die Portabilität ist im eingebetteten Bereich weniger wichtig, ist jedoch ein gutes Argument gegen die Verwendung von alloca () außerhalb sorgfältig kontrollierter Umstände.

Außerhalb des eingebetteten Bereichs habe ich alloca () hauptsächlich innerhalb von Protokollierungs- und Formatierungsfunktionen verwendet, um die Effizienz zu steigern, und in einem nicht rekursiven lexikalischen Scanner, in dem temporäre Strukturen (die mit alloca () zugewiesen wurden, während der Tokenisierung und Klassifizierung erstellt werden, und dann eine dauerhafte Objekt (über malloc () zugewiesen) wird gefüllt, bevor die Funktion zurückkehrt. Die Verwendung von alloca () für die kleineren temporären Strukturen reduziert die Fragmentierung erheblich, wenn das persistente Objekt zugewiesen wird.

Daniel Glasser
quelle
1

Die meisten Antworten hier verfehlen weitgehend den Punkt: Es gibt einen Grund, warum zu verwenden _alloca() möglicherweise schlechter ist als das bloße Speichern großer Objekte im Stapel.

Der Hauptunterschied zwischen automatischer Speicherung und _alloca()letzterer besteht darin, dass letztere unter einem zusätzlichen (schwerwiegenden) Problem leidet: Der zugewiesene Block wird nicht vom Compiler gesteuert , sodass der Compiler ihn nicht optimieren oder recyceln kann.

Vergleichen Sie:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

mit:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

Das Problem mit letzterem sollte offensichtlich sein.

Alecov
quelle
Haben Sie praktische Beispiele, die den Unterschied zwischen VLA und VLA demonstrieren alloca(ja, ich sage VLA, weil allocaes mehr als nur der Schöpfer von Arrays statischer Größe ist)?
Ruslan
Es gibt Anwendungsfälle für den zweiten, die der erste nicht unterstützt. Ich möchte vielleicht 'n' Datensätze haben, nachdem die Schleife 'n' Mal ausgeführt wurde - vielleicht in einer verknüpften Liste oder einem Baum; Diese Datenstruktur wird dann entsorgt, wenn die Funktion schließlich zurückkehrt. Was nicht heißt, dass ich irgendetwas so codieren würde :-)
Greggo
1
Und ich würde sagen, dass "Compiler es nicht kontrollieren kann", weil alloca () so definiert ist; moderne Compiler wissen, was Allokation ist, und behandeln sie besonders; Es ist nicht nur eine Bibliotheksfunktion wie in den 80ern. C99-VLAs sind grundsätzlich Allokationen mit Blockumfang (und besserer Typisierung). Keine mehr oder weniger Kontrolle, nur Anpassung an unterschiedliche Semantik.
Greggo
@greggo: Wenn Sie der Downvoter sind, würde ich gerne hören, warum Sie denken, dass meine Antwort nicht nützlich ist.
Alecov
In C ist das Recycling nicht die Aufgabe des Compilers, sondern die Aufgabe der c-Bibliothek (free ()). alloca () wird bei der Rückgabe freigegeben.
Peterh
1

Ich glaube nicht, dass dies jemand erwähnt hat, aber alloca hat auch einige schwerwiegende Sicherheitsprobleme, die bei malloc nicht unbedingt auftreten müssen (obwohl diese Probleme auch bei stapelbasierten Arrays auftreten, ob dynamisch oder nicht). Da der Speicher auf dem Stapel zugewiesen ist, haben Pufferüberläufe / -unterläufe viel schwerwiegendere Konsequenzen als nur mit malloc.

Insbesondere wird die Rücksprungadresse für eine Funktion auf dem Stapel gespeichert. Wenn dieser Wert beschädigt wird, kann Ihr Code in einen ausführbaren Speicherbereich verschoben werden. Compiler unternehmen große Anstrengungen, um dies zu erschweren (insbesondere durch zufälliges Adresslayout). Dies ist jedoch eindeutig schlimmer als nur ein Stapelüberlauf, da der beste Fall ein SEGFAULT ist, wenn der Rückgabewert beschädigt ist. Es kann jedoch auch ein zufälliger Speicher ausgeführt werden oder im schlimmsten Fall ein Speicherbereich, der die Sicherheit Ihres Programms gefährdet .

Dyllon Gagnier
quelle