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

StackOverflow https://stackoverflow.com/questions/1018853

Question

alloca() alloue de la mémoire sur la pile plutôt que sur le tas, comme dans le cas de malloc().Ainsi, lorsque 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.Libération de la mémoire allouée via malloc() est un casse-tête majeur et, s'il est manqué d'une manière ou d'une autre, cela entraîne toutes sortes de problèmes de mémoire.

Pourquoi l'utilisation de alloca() découragé malgré les caractéristiques ci-dessus ?

Était-ce utile?

La solution

La réponse se trouve juste là dans la page man (au moins sur Linux ):

  

VALEUR RENVOYÉE          La fonction alloca () renvoie un pointeur au début de la   espace alloué. Si la   causes d'allocation          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 beaucoup, et tant que vous n’en abusez pas (alloca énormes valeurs), tout va bien. Une fois que vous dépassez le & Quot; quelques centaines d'octets & Quot; Mark, il est temps d'utiliser malloc et ses amis à la place. Vous pouvez toujours avoir des échecs d'allocation, mais au moins vous aurez une indication de l'échec au lieu de simplement vider la pile.

Autres conseils

L’un des bugs les plus mémorables que j’avais concerne une fonction en ligne qui utilisait alloca. Il s’est manifesté par un débordement de pile (car il alloue sur la pile) en 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();
   }
}

Il s’agit donc de la fonction DoSomething intégrée du compilateur et de toutes les affectations de pile qui se produisaient à l’intérieur de la fonction Process(), ce qui a fait 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 principaux quand je ne pouvais pas le réparer), ce n’était pas tout à fait <=>, c’était l’un des Macros de conversion de chaîne ATL.

La leçon est donc: n'utilisez pas <=> dans les fonctions que vous pensez être en ligne.

Ancienne 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 le standard C99 et existe comme extension de compilateur dans de nombreux compilateurs.

alloca () est très utile si vous ne pouvez pas utiliser une variable locale standard car sa taille doit être déterminée au moment de l'exécution et vous pouvez le faire. garantit absolument 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 renvoie pas le pointeur ni aucun élément qui le contient.
  • ne stocke pas le pointeur dans aucune structure allouée sur le tas
  • ne laissez aucun autre thread utiliser le pointeur

Le véritable danger vient du risque que quelqu'un d'autre enfreigne ces conditions un jour ou l'autre. En gardant cela à l’esprit, c’est bien pour passer des tampons aux fonctions qui formatent le texte:)

Comme indiqué dans la cette publication dans un groupe de discussion , plusieurs raisons pourquoi utiliser alloca peut être considéré comme difficile et dangereux:

  • Tous les compilateurs ne supportent pas <=>.
  • Certains compilateurs interprètent différemment le comportement souhaité de <=>. Par conséquent, la portabilité n'est pas garantie même entre compilateurs qui le prennent en charge.
  • Certaines implémentations sont boguées.

Un problème est que ce n’est pas standard, bien que largement supporté. Toutes choses étant égales par ailleurs, j'utiliserais toujours une fonction standard plutôt qu'une extension commune du compilateur.

  

toujours allouer, l'utilisation est découragée, pourquoi?

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

  • C99 fournit des tableaux de longueur variable, qui seraient souvent utilisés de manière préférentielle car la notation est plus cohérente avec les tableaux de longueur fixe et intuitive globale
  • de nombreux systèmes ont moins de mémoire / d'espace adresse disponible pour la pile que pour le tas, ce qui rend le programme légèrement plus sensible à l'épuisement de la mémoire (débordement de pile): cela peut être considéré comme un avantage ou un inconvénient. Une chose - une des raisons pour lesquelles la pile ne croît pas automatiquement comme le tas, est d'empêcher que des programmes hors de contrôle aient autant d'impact négatif sur toute la machine
  • lorsqu'il est utilisé dans une étendue plus locale (telle qu'une boucle while ou for) ou dans plusieurs étendues, la mémoire s'accumule par itération / étendue et n'est pas libérée jusqu'à la sortie de la fonction: ceci contraste avec les variables normales définies dans le cadre d'une structure de contrôle (par exemple, for {int i = 0; i < 2; ++i) { X } accumulerait alloca - la mémoire demandée sous 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 inline des fonctions qui appellent malloc, mais si vous les forcez, le WonderfulObject_DestructorFree(ptr) se produira dans le contexte de l'appelant (la pile ne sera pas libérée avant le retour de l'appelant)
  • Il y a longtemps WonderfulObject* p = WonderfulObject_AllocConstructor();, une fonctionnalité / hack non portable est passée à une extension standardisée, mais une certaine perception négative peut persister
  • la durée de vie est liée à la portée de la fonction, ce qui peut ou non convenir mieux au programmeur que le contrôle explicite de p
  • devoir utiliser WonderfulObject_AllocConstructor encourage à penser à la désallocation - si elle est gérée via une fonction d'encapsulation (par exemple, free), elle fournit un point de départ pour les opérations de nettoyage d'implémentation (comme la fermeture de descripteurs de fichier, la libération de pointeurs internes ou faire de la journalisation) sans modification explicite du code client: c’est parfois un bon modèle à adopter de manière cohérente
    • dans ce style de programmation pseudo-OO, il est naturel de vouloir quelque chose comme alloca() - cela est possible lorsque le & "; constructeur &"; est une fonction renvoyant de la mémoire realloc (car la mémoire reste allouée après que la fonction a renvoyé la valeur à stocker dans <=>), mais pas si le constructeur & "; constructeur &"; utilise <=>
      • une version macro de <=> pourrait y parvenir, mais & "les macros sont néfastes &"; en ce sens qu'ils peuvent entrer en conflit les uns avec les autres et avec un code non-macro et créer des substitutions inattendues et des problèmes difficiles à diagnostiquer par la suite
    • Les
    • opérations <=> manquantes peuvent être détectées par ValGrind, Purify, etc., mais il manque " destructeur " les appels ne peuvent pas toujours être détectés - un avantage très ténu en termes d'application de l'utilisation prévue; Certaines <=> implémentations (telles que GCC) utilisent une macro intégrée pour <=>. Par conséquent, le remplacement à l'exécution d'une bibliothèque de diagnostics utilisant la mémoire n'est pas possible comme il en est pour <=> / <=> / <=> ( p.ex. clôture électrique)
  • certaines implémentations ont des problèmes subtils: par exemple, depuis 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 dans la pile au milieu de l'espace des arguments de la fonction.

  •   
  

Je sais que cette question est étiquetée C, mais en tant que programmeur C ++, je pensais utiliser C ++ pour illustrer l'utilité potentielle de <=>: le code ci-dessous (et here at ideone ) crée un vecteur suivant les types polymorphes de tailles différentes alloués à la pile (avec une durée de vie liée au retour de fonction) plutôt qu’à un tas.

#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
    }
}

Toutes les autres réponses sont correctes. Cependant, si la chose que vous souhaitez affecter avec alloca() est relativement petite, je pense que c'est une bonne technique, plus rapide et plus pratique que d'utiliser malloc() ou autre.

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

Tout le monde a déjà souligné le gros problème qui est le comportement indéfini potentiel d'un débordement de pile, mais il convient de mentionner que l'environnement Windows dispose d'un excellent mécanisme pour intercepter cela à l'aide d'exceptions structurées (SEH) et de pages de garde. Etant donné que la pile ne croît que si nécessaire, ces pages de garde se trouvent dans des zones non allouées. Si vous y 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 chemin joyeux. Ce n’est pas idéal, mais c’est un autre mécanisme qui permet au moins de savoir que quelque chose a mal tourné lorsque le contenu a été traité. * nix pourrait avoir quelque chose de similaire dont je ne suis pas au courant.

Je vous recommande de limiter la taille maximale de votre allocation en encapsulant alloca et en effectuant un suivi en interne. Si vous étiez vraiment assidu à ce sujet, vous pourriez placer certaines sentinelles en haut de votre fonction pour suivre toutes les allocations allouées dans la portée de la fonction et vérifier son intégrité par rapport au montant maximum autorisé pour votre projet.

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

allouer () c'est sympa et efficace...mais il est aussi profondément brisé.

  • comportement de portée cassé (portée de fonction au lieu de portée de bloc)
  • utilisation incompatible avec malloc (allouer()-ted le pointeur ne doit pas être libéré, vous devez désormais suivre d'où viennent vos pointeurs vers gratuit() seulement ceux avec qui tu es malloc())
  • mauvais comportement lorsque vous utilisez également l'inline (la portée va parfois à la fonction appelante selon que l'appelé est inline 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 puisqu'il ne vérifie pas les limites de la pile de toute façon...)
  • pas de norme ANSI

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

Si vous en avez vraiment besoin en 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 de la portée.Comme je le vois VLA sont une sorte de allouer() fait bien.

Bien sûr, une structure ou un tableau local utilisant une grande partie de l'espace nécessaire est encore meilleur, et si vous ne disposez pas d'une telle allocation de tas importante, l'utilisation de plain malloc() est probablement saine.Je ne vois aucun cas d'utilisation sensé où vous en auriez vraiment besoin non plus allouer() ou VLA.

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

  

Lorsqu'il est utilisé correctement et avec soin, utilisation cohérente de alloca()   (peut-être à l'échelle de l'application) pour gérer de petites allocations de longueur variable   (ou les VLA C99, le cas échéant) peuvent entraîner une pile globale plus faible   croissance qu'une implémentation par ailleurs équivalente utilisant des formats surdimensionnés   matrices locales de longueur fixe. Donc, <=> peut être bon pour votre pile si vous l'utilisez avec précaution.

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

@j_random_hacker a très bien raison dans ses commentaires sous autres réponses: éviter l'utilisation de <=> en faveur de tableaux locaux surdimensionnés ne rend pas votre programme plus sûr contre les débordements de pile (sauf si votre compilateur est suffisamment vieux pour permettre l'inlining de fonctions utilisez <=> dans ce cas, vous devez mettre à niveau ou, sauf si vous utilisez <=> des boucles internes, dans ce cas, vous ne devriez pas ... utiliser <=> des boucles internes).

J'ai travaillé sur des environnements de bureau / serveur et des systèmes intégrés. Beaucoup de systèmes embarqués n'utilisent pas du tout de tas (ils ne lient même pas de 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 se lâche jamais. jamais redémarrage pendant des années, ou la justification plus raisonnable que la mémoire dynamique est dangereuse, car il ne peut pas être certain qu’une application ne fragmentera jamais son tas au point d’épuiser complètement sa mémoire. Il reste donc peu de solutions aux programmeurs intégrés.

<=> (ou VLA) peut être l'outil idéal pour le travail.

J'ai vu le temps & amp; une fois encore où un programmeur crée un tampon alloué par pile & "; assez grand pour gérer tous les cas possibles &"; Dans un arbre d’appel profondément imbriqué, l’utilisation répétée de ce modèle (anti-?) Entraîne une utilisation exagérée de la pile. (Imaginez un arbre d’appel de 20 niveaux de profondeur où, pour chaque niveau, pour des raisons différentes, la fonction sur-alloue aveuglément un tampon de 1024 octets & "Juste pour être sûr &"; Alors qu’elle utilisera généralement 16 ou Une autre solution consiste à utiliser <=> ou des VLA et à n'allouer que la quantité d'espace de la pile dont votre fonction a besoin, afin d'éviter de surcharger inutilement la pile. Espérons qu'une des fonctions de l'arborescence des appels nécessite une allocation supérieure à la normale, les autres utilisent encore leurs petites allocations normales et l'utilisation globale de la pile d'applications est bien moindre que si chaque fonction sur-allouait aveuglément un tampon local. .

Mais si vous choisissez d'utiliser <=> ...

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

Voici pourquoi:

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

Cela ne veut pas dire que quiconque écrirait ce code, mais l'argument de taille auquel vous passez alloca provient presque certainement d'une sorte d'entrée, ce qui pourrait permettre par malveillance de faire en sorte que votre programme <=> soit aussi énorme. 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 ne pas déclarer simplement un petit tampon local de taille fixe?

Pratiquement tout le code utilisant <=> et / ou C99 vlas présente des bugs sérieux qui peuvent entraîner des plantages (si vous avez de la chance) ou un compromis sur les privilèges (si vous n’êtes pas aussi chanceux).

Un endroit où alloca() est plus dangereux que malloc(), c'est le noyau - le noyau d'un système d'exploitation typique possède un espace de pile de taille fixe codé en dur dans l'un de ses en-têtes; ce n'est pas aussi flexible que la pile d'une application. Faire un appel à <=> avec une taille non justifiée peut provoquer le plantage du noyau. Certains compilateurs mettent en garde sur l'utilisation de <=> (et même de VLA) sous certaines options qui devraient être activées lors de la compilation du code du noyau. Dans ce cas, il est préférable d'allouer de la mémoire dans le tas qui n'est pas fixé par un disque dur. limite codée.

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

 _ bloc alloca sur la pile

La conséquence est double:

  1. Le programme se plantera de manière spectaculaire et il sera impossible de dire pourquoi et où il s’est écrasé (la pile se déroulera probablement à une adresse aléatoire en raison du pointeur de trame écrasé).

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

En revanche, si vous écrivez au-delà d'un bloc sur le tas, vous & "juste &"; obtenir la corruption de tas. Le programme se terminera probablement de manière inattendue, mais déroulera la pile correctement, réduisant ainsi le risque d’exécution de code malveillant.

Un des pièges avec alloca est que longjmp le rembobine.

En d’autres termes, si vous enregistrez un contexte avec setjmp, puis jmp_buf de la mémoire, puis <=> dans le contexte, vous risquez de perdre la <=> mémoire (sans aucune sorte d’avis). Le pointeur de pile est de retour à sa place et la mémoire n'est plus réservée; si vous appelez une fonction ou effectuez une autre <=>, vous écraserez l'original <=>.

Pour clarifier, ce à quoi je fais référence ici est une situation dans laquelle <=> ne revient pas hors de la fonction où le <=> a eu lieu! Au lieu de cela, une fonction enregistre le contexte avec <=>; puis alloue de la mémoire avec <=> et finalement un longjmp a lieu dans ce contexte. La mémoire de cette fonction <=> n'est pas entièrement libérée; seulement toute la mémoire allouée depuis le <=>. Bien sûr, je parle d'un comportement observé; aucune exigence de ce genre n'est documentée <=> que je sache.

Dans la documentation, l'accent est généralement mis sur le concept selon lequel <=> la mémoire est associée à une activation de la fonction , et non à un bloc quelconque; que plusieurs invocations de <=> récupèrent simplement plus de mémoire de pile, qui est libérée à la fin de la fonction. Pas si; la mémoire est en fait associée au contexte de la procédure. Lorsque le contexte est restauré avec <=>, l'état antérieur <=> l'est également. C’est une conséquence du fait que le registre du pointeur de pile est lui-même utilisé pour l’allocation, et également (nécessairement) sauvegardé et restauré dans le <=>.

Incidemment, cela, s'il fonctionne de cette manière, fournit un mécanisme plausible pour libérer délibérément la mémoire allouée avec <=>.

Je me suis heurté à cela en tant que cause fondamentale d'un bogue.

Je ne pense pas que quiconque ait mentionné cela: l'utilisation de alloca dans une fonction entravera ou désactivera certaines optimisations qui pourraient autrement être appliquées à 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 cadre dans une fonction; les accès aux cadres se font par rapport au pointeur de pile; donc il y a encore un registre pour 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. Cette optimisation ne peut donc pas être effectuée.

Etant donné la rareté de son utilisation et son statut ombragé en tant que fonction standard, les concepteurs de compilateurs désactivent peut-être toutes les optimisations qui pourraient causer des problèmes avec alloca, si plus qu'un petit effort pour le faire fonctionner avec alloca.

MISE À JOUR: Depuis que des tableaux locaux de longueur variable ont été ajoutés à C et C ++ et que ceux-ci présentent des problèmes de génération de code très similaires au compilateur comme alloca, je vois que la «rareté d'utilisation et le statut louche» ne s'appliquent pas au mécanisme sous-jacent; mais je soupçonne toujours que l’utilisation de alloca ou de VLA tend à compromettre la génération de code au sein d’une fonction qui les utilise. Je souhaiterais recevoir les commentaires des concepteurs de compilateur.

Malheureusement, ce qui est vraiment génial alloca() manque du ccc presque génial. Gcc a malloc().

  1. Il sème le germe de sa propre destruction. Avec return comme destructeur.

  2. Comme realloc() il renvoie un pointeur non valide en cas d'échec, ce qui entraînera une erreur de segmentation sur les systèmes modernes dotés d'une MMU (et 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écursion de la queue et n'utiliser que quelques autres informations de transmission à chaque itération.

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

Notez que <=> n'offre plus rien car il renvoie NULL (qui sera également défaut si le paramètre est affecté) lorsque le système manque de mémoire. C'est à dire. tout ce que vous pouvez faire, c'est cautionner ou simplement essayer de l'assigner de quelque façon que ce soit.

Pour utiliser <=> j'utilise des globales et leur attribue la valeur NULL. Si le pointeur n'est pas NULL, je le libère avant d'utiliser <=>.

Vous pouvez également utiliser <=> comme cas général si vous souhaitez copier des données existantes. Vous devez vérifier le pointeur avant de savoir si vous allez copier ou concaténer après le <=>.

3.2.5.2 Avantages d’alloca

Les processus ne disposent que d’une quantité limitée d’espace disponible dans la pile, soit bien moins que la quantité de mémoire disponible pour malloc().

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

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

Si vous disposez déjà de la taille maximale du bloc de mémoire qui vous convient et que vous souhaitez conserver les contrôles de débordement, vous pouvez procéder comme suit:

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

    (...)
}

En fait, la pile n’est pas garantie par alloca. En effet, l'implémentation de alloca par gcc-2.95 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 utilisation ultérieure de goto. Non, pas pour dire que vous ne devriez jamais l’utiliser, mais parfois, alloca entraîne plus de frais généraux que ce qu’il en retire.

La fonction alloca est excellente et tous les opposants répandent simplement le 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 segment de mémoire, il existe de nombreux avantages pour les programmes de longue durée utilisant la pile sur le segment de mémoire pour les variables ayant une durée de vie prédéfinie. Vous évitez la fragmentation de tas et vous pouvez éviter de développer votre espace de processus avec un 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?

IMHO, alloca est considéré comme une mauvaise pratique car tout le monde a peur d’épuiser la limite de taille de la 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, sans #ifdef _MSC_VER, etc.

Merci! Ce fil m'a amené à m'inscrire sur ce site:)

À mon avis, alloca (), le cas échéant, ne doit être utilisé que de manière limitée. Cela ressemble beaucoup à l'utilisation de & "; Goto &"; Un grand nombre de personnes normalement raisonnables ont une forte aversion non seulement pour l'utilisation de, mais aussi pour l'existence de, alloca ().

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

Lorsqu'ils sont disponibles, les VLA peuvent présenter certains avantages par rapport à alloca (): le compilateur peut générer des contrôles de limite de pile qui captureront les accès hors limites lorsque l'accès par style de tableau est utilisé (je ne sais pas si les compilateurs le font, mais cela peut être fait), 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 des baies de tailles fixes, à la fois automatiques (sur la pile) et statiques (global ou local).

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, ce sont toutes), toute variable allouée à une pile peut être dangereuse car l'adresse de la variable peut être prise et une entrée non contrôlée. les valeurs peuvent permettre toutes sortes de méfaits.

La portabilité pose moins de problèmes 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 incorporé, j’ai utilisé alloca () principalement dans les fonctions de journalisation et de formatage pour plus d’efficacité, et dans un scanner lexical non récursif, dans lequel des structures temporaires (allouées à l’aide de alloca () sont créées lors de la création et de la classification, puis un objet persistant (alloué via malloc ()) est rempli avant le retour de la fonction. L'utilisation de alloca () pour les plus petites structures temporaires réduit considérablement la fragmentation lors de l'affectation de l'objet persistant.

La plupart des réponses ici manquent en grande partie le problème: il y a une raison pour laquelle utiliser _alloca() est potentiellement pire que de simplement stocker des objets volumineux dans la pile.

La principale différence entre le stockage automatique et <=> est 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 l'optimiser ou le recycler.

Comparez:

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

avec:

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

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

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 dans malloc (bien que ces problèmes se posent également dans les baies de pile, qu'elles soient dynamiques ou non). Puisque la mémoire est allouée sur la pile, les débordements / débordements de mémoire tampon ont des conséquences bien plus graves qu’avec malloc.

En particulier, l'adresse de retour d'une fonction est stockée dans la pile. Si cette valeur est corrompue, votre code pourrait être assigné à une région de mémoire exécutable. Les compilateurs font de leur mieux pour rendre cela difficile (notamment en randomisant la disposition des adresses). Cependant, ceci est clairement pire qu’un simple dépassement de capacité de la pile car le meilleur des cas est un SEGFAULT si la valeur de retour est corrompue, mais il peut également commencer à exécuter une partie aléatoire de la mémoire ou, dans le pire des cas, une région de mémoire qui compromet la sécurité de votre programme .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top