Pourquoi devrais-je utiliser malloc () lorsque & # 8220; char bigchar [1u < < 31-1]; & # 8221; fonctionne très bien?

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

  •  22-07-2019
  •  | 
  •  

Question

Quel est l'avantage d'utiliser malloc (en plus du retour NULL en cas d'échec) par rapport aux tableaux statiques? Le programme suivant dévorera tout mon bélier et ne commencera à remplir le swap que si les boucles ne sont pas commentées. Il ne tombe pas en panne.

...

#include <stdio.h>

unsigned int bigint[ 1u << 29 - 1 ];
unsigned char bigchar[ 1u << 31 - 1 ];

int main (int argc, char **argv) {
  int i;
/*   for (i = 0; i < 1u << 29 - 1; i++) bigint[i] = i; */
/*   for (i = 0; i < 1u << 31 - 1; i++) bigchar[i] = i & 0xFF; */

  getchar();
  return 0;
}

...

Après quelques essais et erreurs, j'ai trouvé que le tableau ci-dessus était le plus grand ensemble statique autorisé sur mon ordinateur Intel 32 bits avec GCC 4.3. S'agit-il d'une limite standard, d'une limite du compilateur ou d'une machine? Apparemment, je peux en avoir autant que je veux. Cela segfault, mais seulement si je demande (et essaie d'utiliser) plus que malloc ne me le donnerait de toute façon.

Existe-t-il un moyen de déterminer si un tableau statique a été réellement alloué et si son utilisation est sûre?

EDIT: Je voudrais savoir pourquoi malloc est utilisé pour gérer le tas au lieu de laisser le système de mémoire virtuelle le gérer. Apparemment, je peux dimensionner un tableau plusieurs fois plus grand que celui dont je pense avoir besoin et le système de mémoire virtuelle ne conservera que le matériel nécessaire. Si je n'écris jamais, par exemple la fin (ou le début) de ces énormes tableaux, le programme n’utilise pas la mémoire physique. De plus, si je peux écrire à chaque emplacement, que fait malloc en plus d’incrémenter un pointeur dans le tas ou de rechercher des allocations précédentes dans le même processus?

Note de la rédaction: 1 < < 31 provoque un comportement non défini si int est 32 bits, j'ai donc modifié la question pour lire 1u . Le but de la question est de demander de l’allocation de grands tampons statiques.

Était-ce utile?

La solution

Eh bien, pour deux raisons vraiment:

  1. En raison de la portabilité, étant donné que certains systèmes ne gèrent pas la mémoire virtuelle à votre place.

  2. Vous aurez inévitablement besoin de diviser ce tableau en fragments plus petits pour qu'il soit utile, puis pour garder une trace de tous les morceaux, puis éventuellement lorsque vous commencerez à "libérer". certains des morceaux du tableau dont vous n’avez plus besoin vous poseront le problème de la fragmentation de la mémoire .

Tout compte fait, vous allez finir par mettre en œuvre de nombreuses fonctionnalités de gestion de la mémoire (en réalité, à peu près réimplémenter le malloc) sans bénéficier de la portabilité.

D'où les raisons:

  • Portabilité du code via l’encapsulation et la normalisation de la gestion de la mémoire.

  • Amélioration de la productivité personnelle par la réutilisation du code.

Autres conseils

avec malloc, vous pouvez agrandir et réduire votre tableau: il devient dynamique, vous pouvez donc allouer exactement ce dont vous avez besoin.

Cela s'appelle la gestion de la mémoire personnalisée, je suppose. Vous pouvez le faire, mais vous devrez gérer vous-même ce morceau de mémoire. Vous finiriez par écrire votre propre malloc () sur ce morceau.

En ce qui concerne:

  

Après quelques essais et erreurs, j'ai trouvé le   ci-dessus est le plus grand tableau statique   autorisé sur ma machine Intel 32 bits   avec GCC 4.3. Est-ce une norme   limite, une limite du compilateur ou une machine   limite?

Une limite supérieure dépend de la manière dont l'espace d'adressage virtuel de 4 Go (32 bits) est partitionné entre l'espace utilisateur et l'espace noyau. Pour Linux, je pense que le schéma de partitionnement le plus courant comporte une plage d'adresses de 3 Go pour l'espace utilisateur et d'une plage d'adresses de 1 Go pour l'espace noyau. Le partitionnement est configurable au moment de la construction du noyau. Des scissions de 2 Go / 2 Go et de 1 Go / 3 Go sont également utilisées. Lorsque l'exécutable est chargé, un espace d'adressage virtuel doit être alloué pour chaque objet, que la mémoire réelle soit allouée ou non pour la sauvegarde.

Vous pourrez peut-être allouer ce tableau gigantesque dans un contexte mais pas dans un autre. Par exemple, si votre tableau est membre d'une structure et que vous souhaitez la transmettre. Certains environnements ont une limite de 32 Ko sur la taille de la structure.

Comme mentionné précédemment, vous pouvez également redimensionner votre mémoire pour utiliser exactement ce dont vous avez besoin. Dans les contextes critiques en termes de performances, il est important de ne pas effectuer de pagination vers la mémoire virtuelle si cela peut être évité.

Il n'y a pas d'autre moyen de libérer l'allocation de pile que de sortir du cadre. Ainsi, lorsque vous utilisez réellement l'allocation globale et que la machine virtuelle doit vous allouer de la mémoire réelle, elle est allouée et y restera jusqu'à la fin du programme. Cela signifie que tout processus ne fera que croître dans son utilisation de mémoire virtuelle (les fonctions ont des allocations de pile locales et celles-ci seront "libérées").

Vous ne pouvez pas "garder". la mémoire de pile une fois qu’elle sort du cadre de sa fonction, elle est toujours libérée. Vous devez donc connaître la quantité de mémoire que vous utiliserez au moment de la compilation.

Ce qui se résume alors à combien vous pouvez avoir d'int foo [1 < < 29]. Puisque le premier occupe toute la mémoire (sur 32 bits) et sera (disons: 0x000000), le second résoudra en 0xffffffff ou à peu près. Alors le troisième se résoudrait à quoi? Quelque chose que les pointeurs 32 bits ne peuvent pas exprimer. (rappelez-vous que les réservations de pile sont résolues partiellement au moment de la compilation, à l'exécution, via des décalages, jusqu'où le décalage de la pile est poussé lorsque vous affectez telle ou telle variable).

La réponse est donc quasiment la réponse, une fois que vous avez int foo [1 < < 29], vous ne pouvez plus avoir une profondeur raisonnable de fonctions avec d’autres variables de pile locales.

Vous devriez vraiment éviter de faire cela à moins de savoir ce que vous faites. Essayez de ne demander que la quantité de mémoire dont vous avez besoin. Même s'il n'est pas utilisé ou ne gêne pas d'autres programmes, il peut gâcher le processus lui-même. Il y a deux raisons à cela. Premièrement, sur certains systèmes, en particulier les systèmes 32 bits, l’espace adresse peut être épuisé prématurément dans de rares circonstances. De plus, de nombreux noyaux ont une sorte de limite par processus sur la mémoire réservée / virtuelle / non utilisée. Si votre programme demande de la mémoire à des moments de l'exécution, le noyau peut arrêter le processus s'il demande que la mémoire réservée dépasse cette limite. J'ai vu des programmes s’être effondrés ou s’être arrêtés en raison d’une défaillance de malloc, car ils réservaient des Go de mémoire tout en n’utilisant que quelques Mo.

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