problème d’initialisation grand double tableau

Question idiote d’un nouveau programmeur C … Je reçois une erreur de segmentation dans le code suivant:

#include  int main(void) { double YRaw[4000000]={0}; return 0; } 

En utilisant GDB, j’obtiens le commentaire suivant:

 Program received signal EXC_BAD_ACCESS, Could not access memory. Reason: KERN_PROTECTION_FAILURE at address: 0x00007fff5dd7b148 0x0000000100000f24 in main () at talk2me.c:18 18 double YRaw[4000000]={0}; // set YRaw[memdepth] so index is 0 to memdepth-1 

Tout fonctionne pour trouver si je réduis la taille du tableau YRaw par un facteur de 10. J’ai 6 Go de RAM dans le système, alors pourquoi une erreur se produit-elle? Merci, Gkk

4000000 * sizeof(double) devrait être de l’ordre de 32 Mo. C’est trop volumineux pour la stack, c’est pourquoi vous obtenez une exception. (En bref, la stack a débordé.)

Utilisez malloc() pour l’atsortingbuer à partir du tas, faites-en une static ou faites-en une globale.

En règle générale, l’atsortingbution automatique ne doit être utilisée que pour des objects de taille petite à moyenne. Le seuil est difficile à caractériser, mais 32 Mo est bien supérieur à celui-ci dans la plupart des circonstances.

Mettre à jour:

Il existe plusieurs régions de mémoire dans un processus. (Notez que je vais simplifier les choses pour plus de clarté. Si vous voulez les détails sanglants, lisez la documentation de ld et trouvez le fichier de contrôle de l’éditeur de liens utilisé pour mettre en mémoire les exécutables de votre plate-forme.)

Premièrement, il y a le segment de text (parfois appelé code ) . Celui-ci contient le code réel qui s’exécute et généralement toutes les données constantes. En règle générale, le segment de texte est protégé contre les modifications accidentelles et, dans certains systèmes, la mémoire physique peut en fait être partagée entre des processus exécutant le même programme ou utilisant la même bibliothèque partagée.

Ensuite, il y a les segments data et bss . Ensemble, ces segments contiennent toutes les variables allouées de manière statique. Le segment de données contient les variables initialisées et les variables non initialisées. Elles sont distinctes car les variables non initialisées ne sont connues dans le fichier exécutable que par leurs adresses individuelles et leur taille totale. Ces informations sont utilisées pour demander des pages vierges au système d’exploitation, ce qui explique en partie pourquoi les variables globales non initialisées ont la valeur 0.

Ensuite, il y a la stack et le heap . Ces deux segments sont créés au moment de l’exécution, à partir de la mémoire allouée au processus lors de son chargement et souvent étendus au cours de l’exécution. La stack contient logiquement un enregistrement d’imbrication d’appels, de parameters et de variables locales, bien que les détails puissent être étonnamment différents d’une plate-forme à l’autre. Le tas est le pool de mémoire géré par malloc() et ses amis (et généralement l’ operator new() en C ++). Sur de nombreuses plates-formes, la mappe de mémoire de processus est organisée de sorte que la stack et le segment de mémoire n’interagissent pas, mais cela peut signifier que la taille totale de la stack est supérieure, alors que le segment de mémoire est généralement délimité par le système de mémoire virtuelle.

Avec comme arrière-plan, clarifions l’emplacement de stockage de chaque déclaration.

Toutes les variables globales atterrissent dans le segment data ou bss , uniquement en fonction de leur initialisation ou non. S’ils se trouvent dans le segment de données, ils consortingbueront directement à la taille du fichier de l’exécutable. Dans les deux cas, si l’éditeur de liens a réussi, son stockage est garanti pendant toute la durée du processus.

Les variables déclarées static sont affectées de la même manière qu’une variable globale, mais sans entrée de table de symboles publique. Cela signifie qu’un tampon static non initialisé est situé dans le segment bss et un static initialisé dans le segment de données, mais que son nom est connu uniquement de l’étendue qui peut voir sa définition.

Les allocations du tas ne sont pas garanties pour réussir au moment de l’exécution. Il existe une limite supérieure à la taille totale d’un processus, imposée par la stratégie système et l’architecture matérielle. Par exemple, sur un système 32 bits typique, l’architecture ne peut pas autoriser plus de 4 Go d’adresses à un processus unique.

Voici quelques exemples concrets:

 int foo(void) { double YRaw[4000000]={0}; // ... do something with huge buffer } 

YRaw est ici une variable locale et a une classe de stockage automatique. Il est alloué sur la stack lorsque la fonction est entrée et automatiquement relâché lorsque la fonction est fermée. Cependant, cela ne fonctionnera que si la stack a assez de place pour cela. Si ce n’est pas le cas, la stack déborde, et si vous êtes très chanceux, vous obtenez une sorte d’erreur d’exécution pour indiquer ce fait. (Si vous n’êtes pas aussi chanceux, la stack déborde toujours, mais elle écrit au-dessus de la mémoire allouée à un autre segment, peut-être le segment de texte, et un pirate informatique intelligent pourrait peut-être exécuter du code arbitraire.)

 static double YRaw2[4000000]={0}; int foo(void) { static double YRaw3[4000000]={0}; // ... do something with huge buffer } 

Ici, YRaw2 et YRaw3 sont initialisés, les deux se retrouvent dans le segment de données et sur de nombreuses plates-formes, le fichier exécutable réel contiendra les 4 millions de valeurs 0.0 que vous avez spécifiées comme valeurs initiales. La seule différence entre les deux tampons est une question de scope. YRaw2 peut être utilisé par n’importe quelle fonction du même module, alors que YRaw3 n’est visible que dans la fonction.

 static double YRaw4[4000000]; int foo(void) { static double YRaw5[4000000]; // ... do something with huge buffer } 

Ici, YRaw4 et YRaw5 tous deux dans le segment bss et ne grossissent généralement pas le fichier exécutable lui-même. Encore une fois, les tampons ne diffèrent que par la scope de leurs noms. Ils seront implicitement initialisés à la même valeur 0 que celle spécifiée pour YRaw2 et YRaw3 démarrage du programme.

 double YRaw6[4000000]; int foo(void) { // ... do something with huge buffer } 

Ici, YRaw6 est similaire à YRaw4 ci-dessus, à la différence que son nom a une scope globale et que le tampon peut être partagé avec d’autres modules ainsi qu’avec toutes les fonctions de ce module. Il est stocké dans le segment bss, de sorte que, comme YRaw4 il n’a aucune incidence sur la taille du fichier.

Et finalement, ça peut venir du tas. S’il doit exister pour l’ensemble de l’exécution comme s’il avait été alloué au moment de la compilation, vous pouvez procéder de la manière suivante:

 int foo(void) { static double *YRaw7 = NULL; if (!YRaw7) { // allocate the buffer on the first use YRaw7 = calloc(4000000, sizeof(double)); } // ... do something with huge buffer } 

Ici, YRaw7 est stocké dans le tas, alloué lors de sa première utilisation et jamais libéré jusqu’à la fin du processus. Sur la plupart des plateformes “raisonnables”, ce modèle d’utilisation est à la fois raisonnable et autorisé.

 int foo(void) { double *YRaw8 = calloc(4000000, sizeof(double)); assert(YRaw8 != NULL); // do something with huge buffer // ... // but be careful that all code paths that return also // free the buffer if it was allocated. free(YRaw8); } 

Ici, YRaw8 a la même durée de vie qu’une variable automatique que celle que vous aviez prévue avec votre exemple d’origine, mais il est physiquement stocké dans le tas. Il est probablement sage de vérifier que l’allocation de mémoire a réussi comme je l’ai fait avec l’appel à assert() , mais il n’y a peut-être pas de meilleure réponse au manque de mémoire que de permettre à l’assertion d’échouer.

Une autre subtilité: j’ai utilisé calloc() pour allouer les tampons. Ceci a la propriété intéressante que la mémoire est garantie pour être initialisée à tous les bits nuls si l’allocation réussit. Cependant, cela a pour effet secondaire de devoir écrire (généralement) sur chaque octet de l’allocation pour avoir cet effet. Cela signifie que toutes ces pages de mémoire virtuelle sont non seulement affectées au processus, mais qu’elles doivent toutes être recherchées et chaque octet écrit. L’utilisation de malloc() place donnera généralement de meilleurs résultats, mais au prix coûtant, la mémoire n’est pas garantie.

Enfin, la question évidente est “Où devrais-je allouer ce tampon de toute façon?”

Je ne peux pas vous donner une règle ssortingcte, si ce n’est que les allocations importantes n’appartiennent jamais à la stack. Si la mémoire tampon doit exister pendant la durée de vie du processus, une réponse static non initialisée (au niveau du module ou de la fonction) constitue généralement la bonne réponse.

Si la mémoire tampon a besoin d’une durée de vie différente, pour avoir une taille connue uniquement au moment de l’exécution, ou pour vivre et mourir en réponse à des événements externes lors de l’exécution, elle doit être allouée sur le tas avec malloc() et ses amis, puis publiée avec free() ou éventuellement la fin du processus.

Vous avez essayé de déclarer le tableau entier sur la stack. Même si vous avez un téraoctet de RAM, seule une petite partie fixe de celle-ci sera dédiée à l’espace de stack. De grandes quantités de données doivent être allouées sur le tas, en utilisant malloc :

 #include  int main(void) { double* YRaw = malloc(4000000 * sizeof(double)); memset(YRaw, 0, 4000000 * sizeof(double)); /* ... use it ... */ free(YRaw); /* Give the memory back to the system when you're done */ return 0; } 

Voir aussi: “Quoi et où sont la stack et le tas?”