Forum général.général question sur la structure du code que fait le compilateur (.text, .bss, .heap ...)

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
1
4
août
2019

bonjour à tous,

voila j'essaye de comprendre la structure d'un programme.
Je suis tombé sur cette image et je n'ai plus rien compris a tout ce que j'avais lu sur le sujet.
voici l'image : image structure code

j'ai lu que la taille de la pile était fixé à 8Mo, donc ca veut dire que meme si je n'utilise que 1 Mo de ma pile, j'aurais quand meme une taille de pile qui utilise 8 Mo sur ma RAM ? que ce passe t'il si j'ai besoin d'une pile plus grande ? et si je dépasse les 8Mo que ce passe t'il, segmentation fault ? Seul la taille de pile est limité, ou c'est pareil aussi pour les variables globales ?

pourquoi sur l'image, j'ai le tas qui va venir écrire sur la pile ?

merci d'avance pour vos réponses

  • # Hope this helps

    Posté par  . Évalué à 2.

    Déjà, je dirais que la taille de la pile, ben ça dépend … Dans un contexte desktop, oui, possible. Mais ça risque fortement de dépendre aussi du langage, du compilateur/linker, et pour finir, des paramètres de ces derniers. La taille de la pile fait partie de ces paramètres, plus ou moins cachés selon l'environnement que tu utilises. Il me semble aussi que le principe le plus courant est d'avoir une pile de taille limitée, ce qui conduit à un stack overflow quand tu essayes de la dépasser. Mais peut-être qu'il existe d'autre contexte où la taille de la pile, c'est la mémoire qui reste ?
    Quant à l'embarqué, quand tu n'as que quelques ko a utiliser méticuleusement …

    Sur le schéma, entre le heap et la stack, c'est du … rien !
    Tu consommes de la stack en faisant des appels de fonction (attention aux appels récursifs) et en déclarant des variables locales.
    Tu consommes du heap en faisant des allocations dynamiques (malloc & cie, new en C++).

    Forcément, trop de l'un et/ou de l'autre, c'est mauvais pour la santé du process …

    • [^] # Re: Hope this helps

      Posté par  . Évalué à 1.

      Petite correction:

      Il me semble aussi que le principe le plus courant est d'avoir une pile de taille limitée, ce qui conduit à un stack overflow quand tu essayes de la dépasser.

      Alors oui et non.
      Un stack overflow (ou débordement de pile), tel que compris par la majorité, peut effectivement arriver en cas de remplissage de la pile, mais, bien souvent, point besoin de remplir les X Mo de la pile pour y arriver.

      Soit le code ci-dessous:

      int plop(char *pouet) {
         char le_vilain_tampon[50];
         strcpy(le_vilain_tampon, pouet);
         return(0);
      }
      
      int main(int ac, char **av)
      {
         plop(av[1]);
         return(0);
      }

      Il te suffira d'appeler ton programme avec, en premier argument, une chaine composée d'un peu plus de 50 caractères (dépend des options de compil', de l'archi, etc…) pour avoir un beau stack overflow.

      Pour approfondir, je conseille ce mythique article d'Aleph One

      • [^] # Re: Hope this helps

        Posté par  . Évalué à 2.

        Il te suffira d'appeler ton programme avec, en premier argument, une chaine composée d'un peu plus de 50 caractères (dépend des options de compil', de l'archi, etc…) pour avoir un beau stack overflow.

        C'est plus une corruption de pile due à un débordement de tampon (buffer overflow) qu'un débordement de pile (stack overflow), en fait.

        Un exemple plus classique, en abusant de la récursivité:

        #include <stdlib.h>
        #include <stdio.h>
        #include <string.h>
        #include <sys/resource.h>
        
        static size_t n;
        
        static int chienGeant(char*c)
        {
            char buf[1024];
            strncpy(buf,c,1024);
            printf("%zu:%s\n",++n,buf);
            return chienGeant(&buf[0]);
        }
        
        int main()
        {
            printf("coucou\n");
            struct rlimit l={8192,8192};
            if (setrlimit( RLIMIT_STACK,&l))
            {
                perror("unable to set limit");
            }
            chienGeant("un chient geant, c'est comme un chien mais en plus grand.");
        
            return 0;
        }

        Avec

        warning: all paths through this function will call itself [-Winfinite-recursion]

  • # Quelques réponses.

    Posté par  . Évalué à 3.

    j'ai lu que la taille de la pile était fixé à 8Mo, donc ca veut dire que meme si je n'utilise que 1 Mo de ma pile, j'aurais quand meme une taille de pile qui utilise 8 Mo sur ma RAM ?

    De ce que j'ai lu à droite et à gauche, j'ai l'impression qu'il s'agit d'une limite maximale, mais pas nécessairement de la taille imposée de la stack

    que ce passe t'il si j'ai besoin d'une pile plus grande ?

    Tu peux changer la limite avec ulimit -s.
    Dans l'absolu, 8Mo de pile t'assure quand même beaucoup de "stack frame" et beaucoup de place pour des variables…

    et si je dépasse les 8Mo que ce passe t'il, segmentation fault ?

    Oui.

    Seul la taille de pile est limité, ou c'est pareil aussi pour les variables globales ?

    Les variables globales sont "stockées" dans la pile, donc oui

    pourquoi sur l'image, j'ai le tas qui va venir écrire sur la pile ?

    Euh … il ne va pas venir "écrire" sur la pile. L'image, par les flèches, veut (j'imagine) illustrer la façon dont les deux zones vont être gérée…
    Ci dessous un code d'exemple et sa sortie:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int ac, char **av)
    {
        int a, b, c;
        int *d, *e, *f;
        d = malloc(sizeof(int));
        e = malloc(sizeof(int));
        f = malloc(sizeof(int));
        printf("Stack: %p %p %p\n", &a, &b, &c);
        printf("Heap: %p %p %p\n", d, e, f);
        return (0);
    }

    Résultat:
    Stack: 0x7ffcde6cadb4 0x7ffcde6cadb0 0x7ffcde6cadac
    Heap: 0x55867bd76010 0x55867bd76030 0x55867bd76050

    On voit bien que les adresses de a, b et c décroissent là ou les adresses allouées par malloc croissent.

    Dans l'absolu, pile et tas sont distincts (et doivent le rester ;-))

    • [^] # Re: Quelques réponses.

      Posté par  . Évalué à 4. Dernière modification le 05 août 2019 à 10:41.

      Les variables globales sont "stockées" dans la pile

      Pardon ?????
      Les variables globales ne sont pas stockées dans la pile mais stockées soit dans le segment data soit dans le segment BSS (selon si elles sont initialisées ou pas).

      Il n'y a donc pas de limitation de taille (hormis la taille de la RAM).

      Les vrais naviguent en -42

      • [^] # Re: Quelques réponses.

        Posté par  . Évalué à 0.

        My bad, j'ai confondu "globale" et "locale" (retour de congés, matin, tout ça…)

        Par contre, c'est un peu dur je trouve de se faire "moinsser" pour une erreur alors que le reste de ma réponse contient des choses pertinentes …

        • [^] # Re: Quelques réponses.

          Posté par  . Évalué à 3.

          Oublie le "moinssage", tu n'en seras que plus heureux!

          ⚓ À g'Auch TOUTE! http://afdgauch.online.fr

  • # derniere chose

    Posté par  . Évalué à 1.

    merci pour vos réponses tres complete.

    Ce qu'il me pose probleme c'est que la mémoire est sensé etre contiguë pour le developpeur. Or à un moment le tas va arriver vers la mémoire de la pile et il va y avoir un saut de 8Mo (taille de la pile) pour pouvoir recréer de la mémoire dans le tas

    • [^] # Re: derniere chose

      Posté par  . Évalué à 3.

      Pre-scriptum: en lisant en diagonale certains liens que je cite, il est clair que mes souvenirs sont flous et blindés d'erreurs, mais pas le temps de tout lire (p'tet ce soir, ça me fera pas de mal), je prend juste une petite pause au taf… mea culpa.

      J'attendais de voir un peu les réponses, en espérant que quelqu'un aborde la notion de page… pas de bol. Pour le coup, je risque de dire des choses assez fausses… N'hésites pas a vérifier ce que je raconte, ou mieux, a me corriger…

      Or à un moment le tas va arriver vers la mémoire de la pile

      Oui, ce genre de choses peut arriver. Enfin, pouvais arriver. Si je ne dis pas d'âneries (cf plus haut), les noyaux gèrent de nos jours la mémoire par pages. Ce que tu vois quand tu regardes les adresses mémoire de ton processus, ce sont de fausses adresses, au moins pour le tas.

      Quand tu fais un appel a malloc (ou calloc, ou realloc, peu importe…), celui-ci va demander une page au noyau, qui la lui accordera (ou pas: renvoi de NULL/nullptr). Ensuite, il te fileras de petits morceaux de cette page, à la demande. Ce sont ces morceaux de pages qui, ensemble, vont constituer ton tas.
      Le programmeur voit la mémoire comme linéaire grâce au noyau, et, je crois, de la MMU.

      C'est ici l'intérêt d'avoir agrandit la taille des espaces d'adressage: en 16 bits, la mémoire virtuelle n'aurait jamais pu dépasser les 64Kio par processus. En 32 bits, les 4Gio par processus, et en 64 bits… euh… 264 -1 octets :)
      Notes bien le "par processus", parce qu'il existe un mythe selon lequel les machine avec archi 32 bits ne peuvent pas gérer plus de 4Gio de RAM. C'est faux(bon, ok, dans certains cas, certes).

      • [^] # Re: derniere chose

        Posté par  . Évalué à 4.

        J'attendais de voir un peu les réponses, en espérant que quelqu'un aborde la notion de page…

        Ce n'étais pas le sujet, je pense, de connaître la gestion de mémoire d'un point de vue physique.

        les noyaux gèrent de nos jours la mémoire par pages.

        Cela dépend des architectures, mais majoritairement, oui.

        Ce que tu vois quand tu regardes les adresses mémoire de ton processus, ce sont de fausses adresses, au moins pour le tas.

        En fait, toutes sont virtuelles ou logiques.

        Le programmeur voit la mémoire comme linéaire grâce au noyau, et, je crois, de la MMU.

        Le terme est «flat». La MMU est responsable de plein d'autres choses, dont la pagination, mais c'est devenu d'une complexité redoutable de nos jours.


        Arf, j'ai essayé de demander "linux stack overflow" à Google ;)

        • [^] # Re: derniere chose

          Posté par  . Évalué à 2.

          Ce n'étais pas le sujet, je pense, de connaître la gestion de mémoire d'un point de vue physique.

          l'OP m'avais l'air de vouloir en comprendre un maximum… et vue l'image qu'il a montrée, je pensais que pour sa compréhension,la notion de page devait être abordée. Mais, je me suis peut-être trompé. Une Nième fois :)

          Cela dépend des architectures, mais majoritairement, oui.

          Pourrais-tu, pour ma culture, citer des archis pour lesquelles ce n'est pas le cas?

          En fait, toutes sont virtuelles ou logiques.

          Je n'étais pas sûr que cela concernait l'ensemble des zones, j'ai préféré prendre des gants, le tas me semblant la zone la moins dangereuse pour laquelle affirmer ça…

          Le terme est «flat».

          Oh, ça va, j'ai juste essayé d'utiliser un maximum de mots français… :)

          Arf, j'ai essayé de demander "linux stack overflow" à Google ;)

          Mais quelle idée, en effet :D

          Plus sérieusement, merci de ces précisions.

          • [^] # Re: derniere chose

            Posté par  . Évalué à 5.

            [ MMU] [Memory Pages]
            Pourrais-tu, pour ma culture, citer des archis pour lesquelles ce n'est pas le cas?

            En l'absence de MMU ( MIPS, Cortex-M ou Cortex-R par exemple), jouer de mémoire virtuelle devient compliqué, la plupart des OS «classiques» ne le permettent pas (notamment les *BSD).
            L'usage de linux sans MMU est loin d'être trivial, sans parler de la libc. Pour cela il faut regarder du coté de µClinux.

            Les noyaux «temps-réel» ou minimalistes ont une approche différente et vont privilégier les accès «directs», voire configurer la MMU en ce sens quand elle existe. D'autant que la pagination entraîne la fragmentation de la mémoire dans des système où cette ressource est rare.
            Ce qui n’empêche pas d'avoir parfois une MPU (Cortex R), pour protéger un minimum les accès.
            - Parce qu'ici, c'est la notion même de processus qui est virtuelle :) -

            Le terme est «flat».

            Oh, ça va, j'ai juste essayé d'utiliser un maximum de mots français… :)

            Ah ce sujet, si l'OP nous lit toujours, les adresses (virtuelles) de l'adressage d'un processus se trouvent dans:

            • /proc/[PID]/maps

            Et plein d'autres info dans /proc/[PID]/stat, /proc/[PID]/statm et leur synthèse /proc/[PID]/status.

    • [^] # Re: derniere chose

      Posté par  . Évalué à 4.

      Ce qu'il me pose probleme c'est que la mémoire est sensé etre contiguë pour le developpeur.

      Tout dépend de la mémoire dont on cause. Dans votre cas, il s'agit de l'adressage d'un processus (Process address space), il est virtuel et indépendant de celui des autres processus.

      La structure mm_struct vous donnent en donne les détails, vu du noyau.
      On y retrouve certaines des partitions ELF que vous mentionnez dans le sujet,

      • start_code
      • start_data
      • start_brk
      • start_stack
      • arg_start

      Le schémas que vous donnez est trop simple, par exemple il existera une pile allouée par thread, des segments (region) alloués selon les besoins ( VMA, bibliothèques, mapping divers ) …

      Or à un moment le tas va arriver vers la mémoire de la pile et il va y avoir un saut de 8Mo (taille de la pile) pour pouvoir recréer de la mémoire dans le tas

      Gardez à l'esprit que ce schéma est une représentation virtuelle de la mémoire vu d' un unique processus. Il ne correspond en rien ce qui se passe dans la mémoire physique et n'est pas partagé avec les autres processus. Il est plus probable que vous allez choper un Out of Memory avant que les deux pointeurs ne se rejoignent.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.