Pourquoi l'utilisation d'alloca () n'est-elle pas considérée comme une bonne pratique?

401

alloca()alloue de la mémoire sur la pile plutôt que sur le tas, comme dans le cas de malloc(). Donc, quand je reviens de la routine, la mémoire est libérée. Donc, en fait, cela résout mon problème de libération de mémoire allouée dynamiquement. La libération de la mémoire allouée malloc()est un casse-tête majeur et si elle est manquée, elle entraîne toutes sortes de problèmes de mémoire.

Pourquoi est-il alloca()déconseillé malgré les fonctionnalités ci-dessus?

Vaibhav
la source
40
Juste une petite note. Bien que cette fonction puisse être trouvée dans la plupart des compilateurs, elle n'est pas requise par la norme ANSI-C et pourrait donc limiter la portabilité. Une autre chose est que vous ne devez pas! free () le pointeur que vous obtenez et il est libéré automatiquement après avoir quitté la fonction.
merkuro
9
De plus, une fonction avec alloca () ne sera pas insérée si elle est déclarée comme telle.
Justicle
2
@Justicle, pouvez-vous fournir des explications? Je suis très curieux de savoir ce qui se cache derrière ce comportement
migajek
47
Oubliez tout le bruit sur la portabilité, pas besoin d'appeler free(ce qui est évidemment un avantage) , l'impossibilité de l'inline (les allocations de tas sont évidemment beaucoup plus lourdes) et etc. La seule raison à éviter allocaest pour les grandes tailles. Autrement dit, gaspiller des tonnes de mémoire de pile n'est pas une bonne idée, et vous avez également la possibilité d'un débordement de pile. Si tel est le cas - envisagez d'utiliser malloca/freea
valdo
5
Un autre aspect positif allocaest que la pile ne peut pas être fragmentée comme le tas. Cela pourrait s'avérer utile pour les applications de style run-forever en temps réel, ou même pour les applications critiques pour la sécurité, car la WCRU peut ensuite être analysée statiquement sans recourir à des pools de mémoire personnalisés avec leur propre ensemble de problèmes (pas de localité temporelle, ressource sous-optimale utilisation).
Andreas

Réponses:

245

La réponse est juste là dans la manpage (au moins sous Linux ):

VALEUR RENVOYÉE La fonction alloca () renvoie un pointeur sur le début de l'espace alloué. Si l'allocation entraîne un débordement de pile, le comportement du programme n'est pas défini.

Ce qui ne veut pas dire qu'il ne devrait jamais être utilisé. L'un des projets OSS sur lequel je travaille l'utilise largement, et tant que vous n'en abusez pas (avec allocades valeurs énormes), ça va. Une fois que vous avez dépassé la marque "quelques centaines d'octets", il est temps d'utiliser à la place des mallocamis. Vous pouvez toujours obtenir des échecs d'allocation, mais au moins vous aurez une indication de l'échec au lieu de simplement faire exploser la pile.

Sean Bright
la source
35
Il n'y a donc vraiment aucun problème que vous n'auriez pas non plus à déclarer de grands tableaux?
TED le
88
@Sean: Oui, le risque de débordement de pile est la raison invoquée, mais cette raison est un peu idiote. Tout d'abord parce que (comme Vaibhav le dit) les grands tableaux locaux causent exactement le même problème, mais ne sont pas aussi vilipendés. De plus, la récursivité peut tout aussi facilement faire exploser la pile. Désolé, mais je vous invite à contrer, espérons-le, l'idée dominante selon laquelle la raison donnée dans la page de manuel est justifiée.
j_random_hacker
49
Mon point est que la justification donnée dans la page de manuel n'a aucun sens, car alloca () est exactement aussi "mauvais" que ces autres choses (tableaux locaux ou fonctions récursives) qui sont considérées comme casher.
j_random_hacker
39
@ninjalj: Pas par des programmeurs C / C ++ très expérimentés, mais je pense que beaucoup de gens qui ont peur alloca()n'ont pas la même peur des tableaux locaux ou de la récursivité (en fait, beaucoup de gens qui crieront alloca()feront l'éloge de la récursivité parce qu'elle "a l'air élégante") . Je suis d'accord avec les conseils de Shaun ("alloca () est très bien pour les petites allocations") mais je ne suis pas d'accord avec la mentalité selon laquelle les cadres alloca () sont uniquement mauvais parmi les 3 - ils sont tout aussi dangereux!
j_random_hacker
35
Remarque: Étant donné la stratégie d'allocation de mémoire "optimiste" de Linux, vous n'obtiendrez probablement aucune indication d'échec d'épuisement de tas ... à la place, malloc () vous renverra un joli pointeur non NULL, puis lorsque vous essayez de réellement accéder à l'espace d'adressage vers lequel il pointe, votre processus (ou tout autre processus, de manière imprévisible) sera tué par le tueur OOM. Bien sûr, c'est une "fonctionnalité" de Linux plutôt qu'un problème C / C ++ en soi, mais c'est quelque chose à garder à l'esprit lors du débat sur la question de savoir si alloca () ou malloc () est "plus sûr". :)
Jeremy Friesner
209

L'un des bugs les plus mémorables que j'ai eu a été de faire avec une fonction en ligne utilisée alloca. Il s'est manifesté comme un débordement de pile (car il alloue sur la pile) à des points aléatoires de l'exécution du programme.

Dans le fichier d'en-tête:

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

Dans le fichier d'implémentation:

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

Donc, ce qui s'est passé était la DoSomethingfonction en ligne du compilateur et toutes les allocations de pile se passaient à l'intérieur de la Process()fonction et faisaient ainsi exploser la pile. Pour ma défense (et ce n'est pas moi qui ai trouvé le problème; j'ai dû aller crier à l'un des développeurs seniors quand je n'ai pas pu le réparer), ce n'était pas simple alloca, c'était une conversion de chaîne ATL macros.

La leçon est donc - ne pas utiliser allocadans des fonctions qui, selon vous, pourraient être intégrées.

Igor Zevaka
la source
91
Intéressant. Mais cela ne serait-il pas considéré comme un bug de compilation? Après tout, l'inline a changé le comportement du code (il a retardé la libération de l'espace alloué en utilisant alloca).
sleske
60
Apparemment, au moins GCC tiendra compte de ceci: "Notez que certains usages dans une définition de fonction peuvent le rendre impropre à la substitution en ligne. Parmi ces usages sont: l'utilisation de varargs, l'utilisation d'alloca, [...]". gcc.gnu.org/onlinedocs/gcc/Inline.html
sleske
139
Quel compilateur fumiez-vous?
Thomas Eding
22
Ce que je ne comprends pas, c'est pourquoi le compilateur ne fait pas bon usage de la portée pour déterminer que les allocas dans la sous-portée sont plus ou moins "libérés": le pointeur de pile pourrait revenir à son point avant d'entrer dans la portée, comme ce qui se fait quand retour de fonction (n'est-ce pas?)
moala
8
J'ai rétrogradé, mais la réponse est bien écrite: je suis d'accord avec les autres que vous reprochez alloca pour ce qui est clairement un bug de compilation . Le compilateur a émis une hypothèse erronée dans une optimisation qu'il n'aurait pas dû faire. Travailler autour d'un bogue du compilateur est très bien, mais je ne lui en veux rien d'autre que le compilateur.
Evan Carroll
75

Vieille question mais personne n'a mentionné qu'elle devrait être remplacée par des tableaux de longueur variable.

char arr[size];

au lieu de

char *arr=alloca(size);

C'est dans la norme C99 et existait comme extension de compilateur dans de nombreux compilateurs.

Patrick Schlüter
la source
5
C'est mentionné par Jonathan Leffler dans un commentaire à la réponse d'Arthur Ulfeldt.
ninjalj
2
En effet, mais cela montre aussi à quel point c'est facile, car je ne l'avais pas vu malgré la lecture de toutes les réponses avant de poster.
Patrick Schlüter
6
Une remarque - ce sont des tableaux de longueur variable, pas des tableaux dynamiques. Ces derniers sont redimensionnables et généralement implémentés sur le tas.
Tim Čas
1
Visual Studio 2015 compilant du C ++ a le même problème.
ahcox
2
Linus Torvalds n'aime pas les VLA dans le noyau Linux . À partir de la version 4.20, Linux devrait être presque sans VLA.
Cristian Ciupitu
60

alloca () est très utile si vous ne pouvez pas utiliser une variable locale standard car sa taille doit être déterminée lors de l'exécution et vous pouvez absolument garantir que le pointeur que vous obtenez de alloca () ne sera JAMAIS utilisé après le retour de cette fonction .

Vous pouvez être assez en sécurité si vous

  • ne renvoyez pas le pointeur ou tout ce qui le contient.
  • ne stocke pas le pointeur dans une structure allouée sur le tas
  • ne laissez aucun autre thread utiliser le pointeur

Le vrai danger vient de la possibilité que quelqu'un d'autre viole ces conditions un peu plus tard. Dans cet esprit, il est idéal pour passer des tampons aux fonctions qui mettent en forme du texte dedans :)

Arthur Ulfeldt
la source
12
La fonction VLA (tableau de longueur variable) de C99 prend en charge les variables locales de taille dynamique sans nécessiter explicitement l'allocation alloca ().
Jonathan Leffler
2
neato! trouvé plus d'informations dans la section '3.4 Tableau de longueur variable' de programmersheaven.com/2/Pointers-and-Arrays-page-2
Arthur Ulfeldt
1
Mais ce n'est pas différent de la manipulation avec des pointeurs vers des variables locales. Ils peuvent aussi être dupes ...
glglgl
2
@Jonathan Leffler une chose que vous pouvez faire avec alloca mais que vous ne pouvez pas faire avec VLA est d'utiliser le mot-clé restrict avec eux. Comme ceci: float * restrict proud_used_arr = alloca (sizeof (float) * size); au lieu de flotter fortement_utilisé_arr [taille]. Cela pourrait aider certains compilateurs (gcc 4.8 dans mon cas) à optimiser l'assemblage même si la taille est une constante de compilation. Voir ma question à ce sujet: stackoverflow.com/questions/19026643/using-restrict-with-arrays
Piotr Lopusiewicz
@JonathanLeffler Un VLA est local au bloc qui le contient. En revanche, alloca()alloue de la mémoire qui dure jusqu'à la fin de la fonction. Cela signifie qu'il ne semble pas y avoir de traduction simple et pratique en VLA de f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }. Si vous pensez qu'il est possible de traduire automatiquement les utilisations de allocaen utilisations de VLA mais que vous avez besoin de plus d'un commentaire pour décrire comment, je peux en faire une question.
Pascal Cuoq
40

Comme indiqué dans cette publication de groupe de discussion , il existe plusieurs raisons pour lesquelles l'utilisation allocapeut être considérée comme difficile et dangereuse:

  • Tous les compilateurs ne prennent pas en charge alloca.
  • Certains compilateurs interprètent allocadifféremment le comportement souhaité , de sorte que la portabilité n'est pas garantie même entre les compilateurs qui le prennent en charge.
  • Certaines implémentations sont boguées.
Mémoire libre
la source
24
Une chose que j'ai vue mentionnée sur ce lien qui n'est pas ailleurs sur cette page est qu'une fonction qui utilise alloca()nécessite des registres séparés pour contenir le pointeur de pile et le pointeur de trame. Sur les processeurs x86> = 386, le pointeur de pile ESPpeut être utilisé pour les deux, libérant EBP- à moins qu'il ne alloca()soit utilisé.
j_random_hacker
10
Un autre bon point sur cette page est que, à moins que le générateur de code du compilateur ne le traite comme un cas spécial, il f(42, alloca(10), 43);pourrait se bloquer en raison de la possibilité que le pointeur de pile soit ajusté alloca() après qu'au moins un des arguments ait été poussé dessus.
j_random_hacker
3
Le message lié semble avoir été écrit par John Levine - le mec qui a écrit "Linkers and Loaders", je ferais confiance à tout ce qu'il dit.
user318904
3
Le message lié est une réponse à un message de John Levine.
A. Wilcox
6
Gardez à l'esprit que beaucoup de choses ont changé depuis 1991. Tous les compilateurs C modernes (même en 2009) doivent gérer alloca comme un cas spécial; c'est une fonction intrinsèque plutôt qu'une fonction ordinaire, et peut même ne pas appeler une fonction. Ainsi, la question de l'allocation en paramètre (qui a surgi dans K&R C à partir des années 1970) ne devrait pas être un problème maintenant. Plus de détails dans un commentaire que j'ai fait sur la réponse de Tony D
greggo
26

Un problème est qu'il n'est pas standard, bien qu'il soit largement pris en charge. Toutes choses étant égales par ailleurs, j'utiliserais toujours une fonction standard plutôt qu'une extension de compilateur commune.

David Thornley
la source
21

l'utilisation d'allocations est déconseillée, pourquoi?

Je ne perçois pas un tel consensus. Beaucoup de pros solides; quelques inconvénients:

  • C99 fournit des tableaux de longueur variable, qui seraient souvent utilisés de préférence car la notation est plus cohérente avec les tableaux de longueur fixe et intuitive dans l'ensemble
  • de nombreux systèmes ont moins de mémoire / espace d'adressage global disponible pour la pile que pour le tas, ce qui rend le programme légèrement plus sensible à l'épuisement de la mémoire (par le débordement de la pile): cela peut être considéré comme une bonne ou une mauvaise chose - une L'une des raisons pour lesquelles la pile n'augmente pas automatiquement comme le tas est d'empêcher les programmes incontrôlés d'avoir autant d'impact négatif sur l'ensemble de la machine
  • lorsqu'elle est utilisée dans une étendue plus locale (comme une boucle whileou for) ou dans plusieurs étendues, la mémoire s'accumule par itération / étendue et n'est libérée que lorsque la fonction se termine: cela contraste avec les variables normales définies dans l'étendue d'une structure de contrôle (par ex. for {int i = 0; i < 2; ++i) { X }accumulerait la allocamémoire demandée en X, mais la mémoire d'un tableau de taille fixe serait recyclée par itération).
  • les compilateurs modernes ne font généralement pas inlinefonctionner cet appel alloca, mais si vous les forcez, cela allocase produira dans le contexte des appelants (c'est-à-dire que la pile ne sera pas libérée avant le retour de l'appelant)
  • il y a longtemps est passé allocad'une fonctionnalité / piratage non portable à une extension standardisée, mais une perception négative peut persister
  • la durée de vie est liée à la portée de la fonction, qui peut ou non convenir au programmeur mieux que mallocle contrôle explicite de
  • avoir à utiliser mallocencourage à penser à la désallocation - si elle est gérée via une fonction wrapper (par exemple WonderfulObject_DestructorFree(ptr)), la fonction fournit un point pour les opérations de nettoyage de l'implémentation (comme la fermeture des descripteurs de fichiers, la libération de pointeurs internes ou la journalisation) sans modifications explicites du client code: parfois c'est un joli modèle à adopter systématiquement
    • dans ce style de programmation pseudo-OO, il est naturel de vouloir quelque chose comme WonderfulObject* p = WonderfulObject_AllocConstructor();- c'est possible lorsque le "constructeur" est une fonction renvoyant de la mallocmémoire (car la mémoire reste allouée après que la fonction retourne la valeur à stocker p), mais pas si le "constructeur" utilisealloca
      • une version macro de WonderfulObject_AllocConstructorpourrait y parvenir, mais «les macros sont mauvaises» en ce sens qu'elles peuvent entrer en conflit les unes avec les autres et avec du code non macro et créer des substitutions involontaires et des problèmes difficiles à diagnostiquer en conséquence
    • les freeopérations manquantes peuvent être détectées par ValGrind, Purify, etc., mais les appels «destructeurs» manquants ne peuvent pas toujours être détectés du tout - un avantage très ténu en termes d'application de l'utilisation prévue; certaines alloca()implémentations (telles que GCC) utilisent une macro intégrée pour alloca(), donc la substitution au moment de l'exécution d'une bibliothèque de diagnostic d'utilisation de la mémoire n'est pas possible de la même manière que pour malloc/ realloc/ free(par exemple une clôture électrique)
  • certaines implémentations ont des problèmes subtils: par exemple, à partir de la page de manuel Linux:

    Sur de nombreux systèmes, alloca () ne peut pas être utilisé dans la liste des arguments d'un appel de fonction, car l'espace de pile réservé par alloca () apparaîtrait sur la pile au milieu de l'espace pour les arguments de fonction.


Je sais que cette question est étiquetée C, mais en tant que programmeur C ++, j'ai pensé utiliser C ++ pour illustrer l'utilité potentielle de alloca: le code ci-dessous (et ici à ideone ) crée un vecteur de suivi de types polymorphes de tailles différentes qui sont alloués par pile (avec durée de vie liée au retour de la fonction) plutôt que le tas alloué.

#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
la source
no +1 en raison de la façon idiosyncrasique
louche
@einpoklum: eh bien, c'est très instructif ... merci.
Tony Delroy
1
Permettez-moi de reformuler: c'est une très bonne réponse. Jusqu'au point où je pense que vous suggérez que les gens utilisent une sorte de contre-modèle.
einpoklum
Le commentaire de la page de manuel Linux est très ancien et, j'en suis sûr, obsolète. Tous les compilateurs modernes savent ce qu'est alloca () et ne trébucheront pas sur leurs lacets comme ça. Dans l'ancien K&R C, (1) toutes les fonctions utilisaient des pointeurs de trame (2) Tous les appels de fonction étaient {push args sur pile} {call func} {add # n, sp}. alloca était une fonction lib qui augmenterait simplement la pile, le compilateur ne savait même pas que cela se produisait. (1) et (2) ne sont plus vrais, donc alloca ne peut pas fonctionner de cette façon (maintenant c'est intrinsèque). Dans l'ancien C, appeler alloca au milieu de pousser des arguments briserait évidemment ces hypothèses aussi.
greggo
4
En ce qui concerne l'exemple, je serais généralement préoccupé par quelque chose qui nécessite toujours_inline pour éviter la corruption de la mémoire ....
greggo
14

Toutes les autres réponses sont correctes. Cependant, si la chose que vous souhaitez allouer en utilisant alloca()est raisonnablement petite, je pense que c'est une bonne technique qui est plus rapide et plus pratique que l'utilisation malloc()ou autre.

En d'autres termes, alloca( 0x00ffffff )est dangereux et susceptible de provoquer un débordement, exactement autant quechar hugeArray[ 0x00ffffff ]; est. Soyez prudent et raisonnable et tout ira bien.

JSB ձոգչ
la source
12

Beaucoup de réponses intéressantes à cette "vieille" question, même des réponses relativement nouvelles, mais je n'en ai trouvé aucune qui mentionne cela ....

Lorsqu'elle est utilisée correctement et avec soin, l'utilisation cohérente de alloca() (peut-être à l'échelle de l'application) pour gérer de petites allocations de longueur variable (ou VLA C99, le cas échéant) peut entraîner une croissance globale de la pile inférieure à une implémentation autrement équivalente utilisant des tableaux locaux surdimensionnés de longueur fixe . Cela alloca()peut donc être bon pour votre pile si vous l'utilisez avec précaution.

J'ai trouvé cette citation dans ... OK, j'ai fait cette citation. Mais vraiment, pensez-y ...

@j_random_hacker a tout à fait raison dans ses commentaires sous d'autres réponses: éviter d'utiliser alloca()en faveur de tableaux locaux surdimensionnés ne rend pas votre programme plus sûr contre les débordements de pile (à moins que votre compilateur soit assez vieux pour permettre l'inclusion de fonctions qui utilisent, alloca()auquel cas vous devriez mise à niveau, ou à moins que vous n'utilisiez des alloca()boucles internes, auquel cas vous ne devriez pas ... utiliser des alloca()boucles internes).

J'ai travaillé sur des environnements de bureau / serveur et des systèmes embarqués. Beaucoup de systèmes embarqués n'utilisent pas du tout de tas (ils ne lient même pas en support), pour des raisons qui incluent la perception que la mémoire allouée dynamiquement est mauvaise en raison des risques de fuites de mémoire sur une application qui ne redémarre jamais pendant des années à la fois, ou la justification plus raisonnable que la mémoire dynamique est dangereuse car on ne peut pas savoir avec certitude qu'une application ne fragmentera jamais son tas au point d'épuisement de la fausse mémoire. Les programmeurs intégrés se retrouvent donc avec peu d'alternatives.

alloca() (ou VLA) peut être le bon outil pour le travail.

J'ai vu maintes et maintes fois où un programmeur fait un tampon alloué à la pile "assez grand pour gérer n'importe quel cas possible". Dans un arbre d'appels profondément imbriqué, l'utilisation répétée de ce modèle (anti-?) Conduit à une utilisation exagérée de la pile. (Imaginez un arbre d'appels de 20 niveaux de profondeur, où à chaque niveau pour différentes raisons, la fonction suralloue aveuglément un tampon de 1024 octets "juste pour être sûr" alors qu'en général, elle n'en utilisera que 16 ou moins, et seulement les cas rares peuvent en utiliser davantage.) Une alternative consiste à utiliseralloca()ou VLA et allouez uniquement autant d'espace de pile que votre fonction a besoin, pour éviter de surcharger inutilement la pile. Espérons que lorsqu'une fonction dans l'arborescence des appels nécessite une allocation plus grande que la normale, d'autres dans l'arborescence des appels utilisent toujours leurs petites allocations normales, et l'utilisation globale de la pile d'applications est nettement inférieure à celle si chaque fonction surallouait aveuglément un tampon local. .

Mais si vous choisissez d'utiliser alloca()...

Sur la base d'autres réponses sur cette page, il semble que les VLA devraient être sûrs (ils ne composent pas les allocations de pile s'ils sont appelés à partir d'une boucle), mais si vous utilisez alloca(), veillez à ne pas l'utiliser à l'intérieur d'une boucle et faites assurez- vous que votre fonction ne peut pas être alignée s'il y a une chance qu'elle puisse être appelée dans la boucle d'une autre fonction.

phonetagger
la source
Je suis d'accord avec ce point. Le danger de alloca()est vrai, mais on peut en dire autant des fuites de mémoire malloc()(pourquoi ne pas utiliser un GC alors? On pourrait argumenter). alloca()lorsqu'il est utilisé avec soin, il peut être très utile de réduire la taille de la pile.
Felipe Tonello
Une autre bonne raison de ne pas utiliser de mémoire dynamique, notamment en embarqué: c'est plus compliqué que de s'en tenir à la pile. L'utilisation de la mémoire dynamique nécessite des procédures et des structures de données spéciales, tandis que sur la pile, il s'agit (pour simplifier les choses) d'ajouter / de soustraire un nombre plus élevé de stackpointer.
tehftw
Sidenote: L'exemple "Utilisation d'un tampon fixe [MAX_SIZE]" met en évidence pourquoi la politique de mémoire de surcharge fonctionne si bien. Les programmes allouent de la mémoire à laquelle ils ne peuvent jamais toucher sauf aux limites de leur longueur de mémoire tampon. Donc, c'est bien que Linux (et d'autres systèmes d'exploitation) n'assigne pas réellement une page de mémoire jusqu'à sa première utilisation (par opposition à malloc'd). Si le tampon est supérieur à une page, le programme peut uniquement utiliser la première page et ne pas gaspiller le reste de la mémoire physique.
Katastic Voyage
@KatasticVoyage À moins que MAX_SIZE soit supérieur (ou au moins égal) à la taille de la page de la mémoire virtuelle de votre système, votre argument ne tient pas la route. Également sur les systèmes intégrés sans mémoire virtuelle (de nombreux microcontrôleurs intégrés n'ont pas de MMU), la politique de mémoire de sur-engagement peut être bonne du point de vue "assurez-vous que votre programme fonctionnera dans toutes les situations", mais cette assurance vient avec le prix que la taille de votre pile doit également être alloué pour prendre en charge cette politique de mémoire de surcharge. Sur certains systèmes embarqués, c'est un prix que certains fabricants de produits à bas prix ne sont pas prêts à payer.
phonetagger
11

Tout le monde a déjà souligné la grande chose qui est le comportement non défini potentiel d'un débordement de pile, mais je dois mentionner que l'environnement Windows a un excellent mécanisme pour le détecter en utilisant des exceptions structurées (SEH) et des pages de garde. Étant donné que la pile ne s'agrandit que si nécessaire, ces pages de garde résident dans des zones non allouées. Si vous les allouez (en débordant la pile), une exception est levée.

Vous pouvez intercepter cette exception SEH et appeler _resetstkoflw pour réinitialiser la pile et continuer votre joyeux chemin. Ce n'est pas idéal, mais c'est un autre mécanisme pour au moins savoir que quelque chose a mal tourné lorsque le truc frappe le ventilateur. * nix pourrait avoir quelque chose de similaire que je ne connais pas.

Je recommande de limiter la taille maximale de votre allocation en enveloppant alloca et en la suivant en interne. Si vous étiez vraiment inconditionnel à ce sujet, vous pouvez lancer des sentinelles de portée en haut de votre fonction pour suivre les allocations d'allocations dans la portée de la fonction et vérifier la validité par rapport au montant maximum autorisé pour votre projet.

De plus, en plus de ne pas autoriser les fuites de mémoire, alloca ne provoque pas de fragmentation de la mémoire, ce qui est assez important. Je ne pense pas que l'alloca soit une mauvaise pratique si vous l'utilisez intelligemment, ce qui est fondamentalement vrai pour tout. :-)

SilentDirge
la source
Le problème est que cela alloca()peut exiger tellement d'espace que le pointeur de pile atterrit dans le tas. Avec cela, un attaquant qui peut contrôler la taille alloca()et les données qui entrent dans ce tampon peut écraser le tas (ce qui est très mauvais).
12431234123412341234123
SEH est une chose Windows uniquement. C'est génial si vous ne vous souciez que de votre code s'exécutant sur Windows, mais si votre code doit être multiplateforme (ou si vous écrivez du code qui ne fonctionne que sur une plate-forme non Windows), vous ne pouvez pas compter sur SEH.
George
10

alloca () est agréable et efficace ... mais il est également profondément cassé.

  • comportement de portée cassé (portée de fonction au lieu de portée de bloc)
  • utilisation incompatible avec malloc (le pointeur alloué () ne doit pas être libéré, vous devez désormais suivre d'où viennent les pointeurs vers free () uniquement ceux que vous avez obtenus avec malloc () )
  • mauvais comportement lorsque vous utilisez également l'inlining (la portée va parfois à la fonction d'appelant selon que l'appelé est en ligne ou non).
  • pas de vérification des limites de la pile
  • comportement indéfini en cas d'échec (ne renvoie pas NULL comme malloc ... et que signifie l'échec car il ne vérifie pas les limites de la pile de toute façon ...)
  • pas la norme ansi

Dans la plupart des cas, vous pouvez le remplacer en utilisant des variables locales et une taille majorante. S'il est utilisé pour de gros objets, les mettre sur le tas est généralement une idée plus sûre.

Si vous en avez vraiment besoin C vous pouvez utiliser VLA (pas de vla en C ++, dommage). Ils sont bien meilleurs que alloca () en ce qui concerne le comportement et la cohérence des portées. Comme je le vois, les VLA sont une sorte d' alloca () bien fait.

Bien sûr, une structure ou un tableau local utilisant un majorant de l'espace nécessaire est encore mieux, et si vous n'avez pas une telle allocation de tas majorant en utilisant plain malloc () est probablement sain. Je ne vois aucun cas d'utilisation sensé où vous avez vraiment vraiment besoin d' alloca () ou de VLA.

kriss
la source
Je ne vois pas la raison du vote négatif (sans aucun commentaire, soit dit en passant)
gd1
Seuls les noms ont une portée. allocane crée pas de nom, seulement une plage de mémoire, qui a une durée de vie .
curiousguy
@curiousguy: vous jouez simplement avec des mots. Pour les variables automatiques, je pourrais aussi bien parler de la durée de vie de la mémoire sous-jacente car elle correspond à la portée du nom. Quoi qu'il en soit, le problème n'est pas la façon dont nous l'appelons, mais l'instabilité de la durée de vie / étendue de la mémoire renvoyée par alloca et le comportement exceptionnel.
kriss
2
Je souhaite qu'alloca ait eu un "freea" correspondant, avec une spécification selon laquelle appeler "freea" annulerait les effets de l '"alloca" qui a créé l'objet et tous les suivants, et une exigence selon laquelle le stockage "alloué" dans une fonction doit être «libre» en son sein également. Cela aurait permis à presque toutes les implémentations de prendre en charge alloca / freea de manière compatible, aurait atténué les problèmes inline et généralement rendu les choses beaucoup plus propres.
supercat
2
@supercat - Je le souhaite aussi. Pour cette raison (et plus), j'utilise une couche d'abstraction (principalement des macros et des fonctions en ligne) afin de ne jamais appeler allocaou mallocou freedirectement. Je dis des choses comme {stack|heap}_alloc_{bytes,items,struct,varstruct}et {stack|heap}_dealloc. Donc, heap_deallocjuste des appels freeet stack_deallocc'est un no-op. De cette façon, les allocations de pile peuvent facilement être rétrogradées en allocations de tas, et les intentions sont également plus claires.
Todd Lehman
9

Voici pourquoi:

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

Ce n'est pas que quelqu'un écrive ce code, mais l'argument de taille allocaauquel vous passez provient presque certainement d'une sorte d'entrée, qui pourrait viser malicieusement à obtenir votre programmealloca quelque chose d'énorme comme ça. Après tout, si la taille n'est pas basée sur l'entrée ou n'a pas la possibilité d'être grande, pourquoi n'avez-vous pas simplement déclaré un petit tampon local de taille fixe?

Pratiquement tout le code utilisant allocaet / ou vlas C99 comporte de graves bogues qui entraîneront des plantages (si vous avez de la chance) ou des compromis de privilèges (si vous n'êtes pas aussi chanceux).

R .. GitHub ARRÊTER D'AIDER LA GLACE
la source
1
Le monde pourrait ne jamais le savoir. :( Cela dit, j'espère que vous pourrez clarifier une question que j'ai à propos alloca. Vous avez dit que presque tout le code qui l'utilise avait un bug, mais je prévoyais de l'utiliser; j'ignorerais normalement une telle affirmation, mais à venir je ne vais pas. J'écris une machine virtuelle et j'aimerais allouer des variables qui n'échappent pas à la fonction sur la pile, au lieu de dynamiquement, à cause de l'énorme accélération. Y a-t-il une alternative approche qui a les mêmes caractéristiques de performance? Je sais que je peux me rapprocher des pools de mémoire, mais ce n'est toujours pas aussi bon marché. Que feriez-vous?
GManNickG
7
Vous savez ce qui est aussi dangereux? C'est *0 = 9;incroyable! Je suppose que je ne devrais jamais utiliser de pointeurs (ou du moins les déréférencer). Euh, attends. Je peux tester pour voir si elle est nulle. Hmm. Je suppose que je peux également tester la taille de la mémoire que je veux allouer via alloca. Homme bizarre. Bizarre.
Thomas Eding
7
*0=9;n'est pas valide C. Quant au test de la taille à laquelle vous passez alloca, testez-le par rapport à quoi? Il n'y a aucun moyen de connaître la limite, et si vous allez simplement la tester par rapport à une petite taille fixe sûre connue (par exemple 8k), vous pourriez tout aussi bien utiliser un tableau de taille fixe sur la pile.
R .. GitHub ARRÊTER D'AIDER LA GLACE
7
Le problème avec votre argument "soit que la taille est connue pour être suffisamment petite soit dépendante de l'entrée et pourrait donc être arbitrairement grande" comme je le vois, c'est qu'elle s'applique tout aussi fortement à la récursivité. Un compromis pratique (dans les deux cas) consiste à supposer que si la taille est limitée, small_constant * log(user_input)nous avons probablement suffisamment de mémoire.
j_random_hacker
1
En effet, vous avez identifié le cas UN où VLA / alloca est utile: des algorithmes récursifs où l'espace maximum nécessaire à n'importe quelle trame d'appel pourrait être aussi grand que N, mais où la somme de l'espace nécessaire à tous les niveaux de récursivité est N ou une fonction de N qui ne croît pas rapidement.
R .. GitHub STOP HELPING ICE
9

Je ne pense pas que quiconque ait mentionné cela: l'utilisation d'alloca dans une fonction gênera ou désactivera certaines optimisations qui pourraient autrement être appliquées dans la fonction, car le compilateur ne peut pas connaître la taille du cadre de pile de la fonction.

Par exemple, une optimisation courante par les compilateurs C consiste à éliminer l'utilisation du pointeur de trame dans une fonction, les accès aux trames sont effectués par rapport au pointeur de pile à la place; il y a donc un autre registre à usage général. Mais si alloca est appelé dans la fonction, la différence entre sp et fp sera inconnue pour une partie de la fonction, donc cette optimisation ne peut pas être effectuée.

Compte tenu de la rareté de son utilisation et de son statut louche en tant que fonction standard, les concepteurs de compilateurs désactivent très probablement toute optimisation qui pourrait causer des problèmes avec alloca, si cela prendrait plus qu'un petit effort pour le faire fonctionner avec alloca.

MISE À JOUR: Étant donné que des tableaux locaux de longueur variable ont été ajoutés à C et que ceux-ci présentent des problèmes de génération de code très similaires au compilateur en tant qu'alloca, je vois que la `` rareté d'utilisation et le statut ombragé '' ne s'appliquent pas au mécanisme sous-jacent; mais je soupçonnerais toujours que l'utilisation de alloca ou VLA tend à compromettre la génération de code au sein d'une fonction qui les utilise. Je serais heureux de recevoir les commentaires des concepteurs de compilateurs.

greggo
la source
1
Les tableaux de longueur variable n'ont jamais été ajoutés à C ++.
Nir Friedman
@NirFriedman En effet. Je pense qu'il y avait une liste de fonctionnalités de Wikipédia qui était basée sur une ancienne proposition.
greggo
> Je soupçonnerais toujours que l'utilisation d'alloca ou de VLA tend à compromettre la génération de code. Je pense que l'utilisation d'alloca nécessite un pointeur de trame, car le pointeur de pile se déplace d'une manière qui n'est pas évidente au moment de la compilation. alloca peut être appelé dans une boucle pour continuer à récupérer plus de mémoire de pile, ou avec une taille calculée au moment de l'exécution, etc. S'il y a un pointeur de trame, le code généré a une référence stable aux locaux et le pointeur de pile peut faire ce qu'il veut; il n'est pas utilisé.
Kaz
8

Un écueil allocaest quelongjmp cela rembobine.

Autrement dit, si vous enregistrez un contexte avec setjmp, puis de la allocamémoire, puis longjmpdans le contexte, vous risquez de perdre la allocamémoire. Le pointeur de pile est de retour où il était et donc la mémoire n'est plus réservée; si vous appelez une fonction ou en faites une autre alloca, vous encombrerez l'original alloca.

Pour clarifier, ce à quoi je fais spécifiquement référence ici est une situation dans laquelle longjmpil ne revient pas de la fonction où il a allocaeu lieu! Au contraire, une fonction enregistre le contexte avec setjmp; alloue ensuite de la mémoire avec allocaet enfin un longjmp a lieu dans ce contexte. La allocamémoire de cette fonction n'est pas entièrement libérée; juste toute la mémoire qu'il a allouée depuis le setjmp. Bien sûr, je parle d'un comportement observé; aucune exigence de ce genre n'est documentée à allocama connaissance.

Dans la documentation, l'accent est généralement mis sur le concept selon lequel la allocamémoire est associée à une activation de fonction , et non à un bloc; que plusieurs invocations de allocasimplement saisir plus de mémoire de pile qui est tout libéré lorsque la fonction se termine. Pas si; la mémoire est en fait associée au contexte de la procédure. Lorsque le contexte est restauré avec longjmp, il en est de même de l' allocaétat antérieur . C'est une conséquence du registre du pointeur de pile lui-même utilisé pour l'allocation, et également (nécessairement) enregistré et restauré dans le jmp_buf.

Soit dit en passant, cela, s'il fonctionne de cette façon, fournit un mécanisme plausible pour libérer délibérément la mémoire allouée avec alloca.

J'ai rencontré cela comme la cause première d'un bug.

Kaz
la source
1
C'est ce qu'il est censé faire si - longjmpretourne et rend donc les oublie programme sur tout ce qui est arrivé dans la pile: toutes les variables, les appels de fonction , etc. Et allocaest comme un tableau sur la pile, il est donc prévu qu'ils seront soient faussés comme tout le reste sur la pile.
tehftw
1
man allocaa donné la phrase suivante: "Parce que l'espace alloué par alloca () est alloué dans le cadre de la pile, cet espace est automatiquement libéré si le retour de la fonction est sauté par un appel à longjmp (3) ou siglongjmp (3).". Il est donc documenté que la mémoire allouée à allocaest détruite après a longjmp.
2018
@tehftw La situation décrite se produit sans qu'un saut de fonction ne soit sauté par longjmp. La fonction cible n'est pas encore retournée. Il l'a fait setjmp, allocaet puis longjmp. Le longjmppeut revenir en arrière le allocados de l' Etat à ce qu'il était au setjmptemps. C'est-à-dire que le pointeur déplacé par allocasouffre du même problème qu'une variable locale qui n'a pas été marquée volatile!
Kaz
3
Je ne comprends pas pourquoi ce serait censé être inattendu. Lorsque vous setjmpalors alloca, puis longjmp, il est normal que allocaserait rembobinées. Le but longjmpest de revenir à l'état dans lequel il a été enregistré setjmp!
tehftw
@tehftw Je n'ai jamais vu cette interaction particulière documentée. Par conséquent, il ne peut pas être invoqué dans les deux sens, sauf par une enquête empirique avec des compilateurs.
Kaz
7

Un endroit où alloca()est particulièrement dangereux que malloc()le noyau - le noyau d'un système d'exploitation typique a un espace de pile de taille fixe codé en dur dans l'un de ses en-têtes; il n'est pas aussi flexible que la pile d'une application. Faire un appel à alloca()avec une taille non garantie peut entraîner le plantage du noyau. Certains compilateurs avertissent l'utilisation de alloca()(et même des VLA d'ailleurs) sous certaines options qui doivent être activées lors de la compilation d'un code de noyau - ici, il est préférable d'allouer de la mémoire dans le tas qui n'est pas fixée par une limite codée en dur.

Sondhi Chakraborty
la source
7
alloca()n'est pas plus dangereux que int foo[bar];barest un entier arbitraire.
Todd Lehman du
@ToddLehman C'est exact, et pour cette raison exacte, nous avons banni les VLA dans le noyau pendant plusieurs années, et nous sommes sans VLA depuis 2018 :-)
Chris Down
6

Si vous écrivez accidentellement au-delà du bloc alloué avec alloca(en raison d'un débordement de tampon par exemple), alors vous écraserez l' adresse de retour de votre fonction, car celle-ci est située "au-dessus" de la pile, c'est- à- dire après votre bloc alloué.

bloc _alloca sur la pile

La conséquence en est double:

  1. Le programme se bloquera de façon spectaculaire et il sera impossible de dire pourquoi ou où il s'est bloqué (la pile se déroulera très probablement vers une adresse aléatoire en raison du pointeur de trame écrasé).

  2. Cela rend le débordement de tampon beaucoup plus dangereux, car un utilisateur malveillant peut créer une charge utile spéciale qui serait placée sur la pile et peut donc être exécutée.

En revanche, si vous écrivez au-delà d'un bloc sur le tas, vous obtenez «juste» une corruption de tas. Le programme se terminera probablement de façon inattendue mais déroulera la pile correctement, réduisant ainsi les risques d'exécution de code malveillant.

rustyx
la source
11
Rien dans cette situation n'est radicalement différent des dangers de débordement de tampon d'un tampon alloué par pile de taille fixe. Ce danger n'est pas unique à alloca.
phonetagger
2
Bien sûr que non. Mais veuillez vérifier la question d'origine. La question est: quel est le danger allocapar rapport à malloc(donc pas de tampon de taille fixe sur la pile).
rustyx
Point mineur, mais les piles sur certains systèmes augmentent (par exemple les microprocesseurs PIC 16 bits).
EBlake
5

Malheureusement, le vraiment génial alloca()manque dans le tcc presque génial. Gcc a alloca().

  1. Il sème la graine de sa propre destruction. Avec retour comme destructeur.

  2. Comme malloc()s'il retourne un pointeur invalide en cas d'échec qui se produira par défaut sur les systèmes modernes avec une MMU (et, espérons-le, redémarrera ceux qui n'en ont pas).

  3. Contrairement aux variables automatiques, vous pouvez spécifier la taille au moment de l'exécution.

Cela fonctionne bien avec la récursivité. Vous pouvez utiliser des variables statiques pour obtenir quelque chose de similaire à la récursivité de queue et utiliser seulement quelques autres informations de passage à chaque itération.

Si vous poussez trop profondément, vous êtes assuré d'un défaut de segmentation (si vous avez une MMU).

Notez qu'il malloc()n'en offre pas plus car il retourne NULL (qui sera également un défaut de segmentation s'il est attribué) lorsque le système est à court de mémoire. C'est-à-dire que tout ce que vous pouvez faire est de cautionner ou simplement essayer de l'attribuer de quelque façon que ce soit.

Pour utiliser, malloc()j'utilise des globaux et je les attribue NULL. Si le pointeur n'est pas NULL, je le libère avant de l'utiliser malloc().

Vous pouvez également utiliser realloc()comme cas général si vous souhaitez copier des données existantes. Vous devez vérifier le pointeur avant de déterminer si vous allez copier ou concaténer après le realloc().

3.2.5.2 Avantages de alloca

Zagam
la source
4
En fait, la spécification alloca ne dit pas qu'elle retourne un pointeur invalide en cas d'échec (débordement de pile), elle dit qu'elle a un comportement indéfini ... et pour malloc, elle dit qu'elle retourne NULL, pas un pointeur invalide aléatoire (OK, l'implémentation de mémoire optimiste Linux fait que inutile).
kriss
@kriss Linux peut tuer votre processus, mais au moins il ne s'aventure pas dans un comportement indéfini
craig65535
@ craig65535: l'expression comportement non défini signifie généralement que ce comportement n'est pas défini par la spécification C ou C ++. En aucun cas, il ne sera aléatoire ou instable sur un système d'exploitation ou un compilateur donné. Par conséquent, il est inutile d'associer UB au nom d'un système d'exploitation comme "Linux" ou "Windows". Cela n'a rien à voir avec ça.
kriss
J'essayais de dire que malloc retournant NULL, ou dans le cas de Linux, un accès mémoire tuant votre processus, est préférable au comportement indéfini d'alloca. Je pense que j'ai dû mal lire votre premier commentaire.
craig65535
3

Les processus ne disposent que d'une quantité limitée d'espace de pile disponible, bien inférieure à la quantité de mémoire disponible malloc().

En utilisant, alloca()vous augmentez considérablement vos chances d'obtenir une erreur de débordement de pile (si vous avez de la chance, ou un plantage inexplicable si vous ne l'êtes pas).

RichieHindle
la source
Cela dépend beaucoup de l'application. Il n'est pas inhabituel pour une application intégrée à mémoire limitée d'avoir une taille de pile supérieure au tas (s'il existe même un tas).
EBlake
3

Pas très joli, mais si les performances comptent vraiment, vous pouvez préallouer de l'espace sur la pile.

Si vous avez déjà la taille maximale du bloc de mémoire dont vous avez besoin et que vous souhaitez conserver les vérifications de débordement, vous pouvez faire quelque chose comme:

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

    (...)
}
Sylvain Rodrigue
la source
12
Le tableau char est-il garanti d'être correctement aligné pour tout type de données? alloca fournit une telle promesse.
Juho Östman
@ JuhoÖstman: vous pouvez utiliser un tableau de struct (ou de n'importe quel type) au lieu de char si vous avez des problèmes d'alignement.
kriss
C'est ce qu'on appelle un tableau à longueur variable . Il est pris en charge en C90 et supérieur, mais pas en C ++. Voir Puis-je utiliser un tableau de longueur variable C en C ++ 03 et C ++ 11?
2015
3

La fonction alloca est géniale et tous les opposants répandent simplement du FUD.

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

Array et parray sont EXACTEMENT les mêmes avec EXACTEMENT les mêmes risques. Dire que l'un est meilleur qu'un autre est un choix syntaxique et non technique.

En ce qui concerne le choix des variables de pile par rapport aux variables de tas, il y a BEAUCOUP d'avantages aux programmes de longue durée utilisant la pile sur le tas pour les variables avec des durées de vie in-scope. Vous évitez la fragmentation du tas et vous pouvez éviter d'agrandir votre espace de processus avec de l'espace de tas inutilisé (inutilisable). Vous n'avez pas besoin de le nettoyer. Vous pouvez contrôler l'allocation de pile sur le processus.

Pourquoi est-ce mauvais?

mlwmohawk
la source
3

En fait, alloca n'est pas garanti d'utiliser la pile. En effet, l'implémentation gcc-2.95 d'alloca alloue de la mémoire à partir du tas en utilisant malloc lui-même. De plus, cette implémentation est boguée, elle peut entraîner une fuite de mémoire et un comportement inattendu si vous l'appelez à l'intérieur d'un bloc avec une nouvelle utilisation de goto. Non, pour dire que vous ne devriez jamais l'utiliser, mais parfois, alloca entraîne plus de frais généraux qu'il n'en libère.

user7491277
la source
Il semble que gcc-2.95 ait rompu l'alloca et ne puisse probablement pas être utilisé en toute sécurité pour les programmes qui en ont besoin alloca. Comment aurait-il nettoyé la mémoire lorsqu'il longjmpest utilisé pour abandonner les trames qui l'ont fait alloca? Quand est-ce que quelqu'un utiliserait gcc 2.95 aujourd'hui?
Kaz
2

À mon humble avis, alloca est considéré comme une mauvaise pratique car tout le monde a peur d'épuiser la limite de taille de pile.

J'ai beaucoup appris en lisant ce fil et quelques autres liens:

J'utilise alloca principalement pour rendre mes fichiers C simples compilables sur msvc et gcc sans aucune modification, style C89, pas de #ifdef _MSC_VER, etc.

Merci ! Ce fil m'a fait m'inscrire à ce site :)

ytoto
la source
Gardez à l'esprit qu'il n'y a pas de "fil" sur ce site. Stack Overflow a un format de questions et réponses, pas un format de fil de discussion. "Répondre" n'est pas comme "Répondre" dans un forum de discussion; cela signifie que vous fournissez réellement une réponse à la question et que vous ne devez pas être utilisé pour répondre à d'autres réponses ou commenter le sujet. Une fois que vous avez au moins 50 représentants, vous pouvez poster des commentaires , mais assurez-vous de lire le "Quand ne devrais- je pas commenter?" section. Veuillez lire la page À propos pour mieux comprendre le format du site.
Adi Inbar
1

À mon avis, alloca (), lorsqu'elle est disponible, ne doit être utilisée que de manière contrainte. Tout comme l'utilisation de "goto", un assez grand nombre de personnes par ailleurs raisonnables ont une forte aversion non seulement pour l'utilisation de alloca (), mais aussi pour son existence.

Pour une utilisation intégrée, où la taille de la pile est connue et où des limites peuvent être imposées via la convention et l'analyse de la taille de l'allocation, et où le compilateur ne peut pas être mis à niveau pour prendre en charge C99 +, l'utilisation de alloca () est très bien, et j'ai été connu pour l'utiliser.

Lorsqu'ils sont disponibles, les VLA peuvent avoir certains avantages par rapport à alloca (): le compilateur peut générer des vérifications de limites de pile qui intercepteront l'accès hors limites lorsque l'accès au style de tableau est utilisé (je ne sais pas si des compilateurs le font, mais il peut et l'analyse du code peut déterminer si les expressions d'accès au tableau sont correctement délimitées. Notez que, dans certains environnements de programmation, tels que l'automobile, les équipements médicaux et l'avionique, cette analyse doit être effectuée même pour les baies de taille fixe, à la fois automatique (sur la pile) et statique (globale ou locale).

Sur les architectures qui stockent à la fois des données et des adresses de retour / pointeurs de trame sur la pile (d'après ce que je sais, c'est tout), toute variable allouée à la pile peut être dangereuse car l'adresse de la variable peut être prise et des valeurs d'entrée non contrôlées peuvent permettre toutes sortes de méfaits.

La portabilité est moins préoccupante dans l'espace intégré, mais c'est un bon argument contre l'utilisation de alloca () en dehors de circonstances soigneusement contrôlées.

En dehors de l'espace intégré, j'ai utilisé alloca () principalement à l'intérieur des fonctions de journalisation et de formatage pour plus d'efficacité, et dans un scanner lexical non récursif, où des structures temporaires (allouées en utilisant alloca () sont créées lors de la tokenisation et de la classification, puis une persistance l'objet (alloué via malloc ()) est rempli avant le retour de la fonction. L'utilisation d'alloca () pour les petites structures temporaires réduit considérablement la fragmentation lorsque l'objet persistant est alloué.

Daniel Glasser
la source
1

La plupart des réponses manquent ici: il y a une raison pour laquelle utiliser _alloca() est potentiellement pire que de simplement stocker de gros objets dans la pile.

La principale différence entre le stockage automatique et le _alloca()fait que ce dernier souffre d'un problème supplémentaire (grave): le bloc alloué n'est pas contrôlé par le compilateur , il n'y a donc aucun moyen pour le compilateur de l'optimiser ou de le recycler.

Comparer:

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

avec:

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

Le problème avec ce dernier devrait être évident.

alecov
la source
Avez-vous des exemples pratiques démontrant la différence entre VLA et alloca(oui, je dis bien VLA, car il allocaest plus qu'un simple créateur de tableaux de taille statique)?
Ruslan
Il existe des cas d'utilisation pour le second, que le premier ne prend pas en charge. Je peux vouloir avoir «n» d'enregistrements une fois la boucle terminée en exécutant «n» fois - peut-être dans une liste chaînée ou un arbre; cette structure de données est ensuite supprimée lorsque la fonction revient finalement. Ce qui ne veut pas dire que je
coderais
1
Et je dirais que "le compilateur ne peut pas le contrôler" parce que c'est ainsi que alloca () est définie; les compilateurs modernes savent ce qu'est alloca et le traitent spécialement; ce n'est pas seulement une fonction de bibliothèque comme c'était le cas dans les années 80. Les VLA C99 sont essentiellement des alloca avec une portée de bloc (et un meilleur typage). Pas plus ou moins de contrôle, juste conforme à des sémantiques différentes.
greggo
@greggo: Si vous êtes le downvoter, je serais heureux d'entendre pourquoi vous pensez que ma réponse n'est pas utile.
alecov
En C, le recyclage n'est pas la tâche du compilateur, c'est plutôt la tâche de la bibliothèque c (free ()). alloca () est libérée au retour.
peterh
1

Je ne pense pas que quiconque ait mentionné cela, mais alloca a également de sérieux problèmes de sécurité qui ne sont pas nécessairement présents avec malloc (bien que ces problèmes surviennent également avec les tableaux basés sur la pile, dynamiques ou non). Étant donné que la mémoire est allouée sur la pile, les débordements / débordements de tampon ont des conséquences beaucoup plus graves qu'avec un simple malloc.

En particulier, l'adresse de retour d'une fonction est stockée sur la pile. Si cette valeur est corrompue, votre code pourrait être envoyé à n'importe quelle région exécutable de la mémoire. Les compilateurs se donnent beaucoup de mal pour rendre cela difficile (en particulier en randomisant la disposition des adresses). Cependant, c'est clairement pire qu'un simple débordement de pile car le meilleur des cas est un SEGFAULT si la valeur de retour est corrompue, mais il pourrait également commencer à exécuter un morceau de mémoire aléatoire ou dans le pire des cas une région de mémoire qui compromet la sécurité de votre programme .

Dyllon Gagnier
la source