66 votes

Comment le compilateur alloue-t-il la mémoire sans connaître la taille au moment de la compilation ?

J'ai écrit un programme C qui accepte une entrée entière de l'utilisateur, qui est utilisée comme la taille d'un tableau d'entiers, et en utilisant cette valeur, il déclare un tableau de taille donnée, et je le confirme en vérifiant la taille du tableau.

Code :

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%ld",sizeof(k));
    return 0;
}

et étonnamment, c'est correct ! Le programme est capable de créer le tableau de la taille requise.
Mais toute l'allocation de mémoire statique est effectuée au moment de la compilation, et pendant la compilation, la valeur de l'option n n'est pas connue, alors comment se fait-il que le compilateur soit capable d'allouer la mémoire de la taille requise ?

Si nous pouvons allouer la mémoire nécessaire juste comme ça, alors quelle est l'utilité de l'allocation dynamique en utilisant malloc() y calloc() ?

0 votes

Ça ne semble pas correct. Quel compilateur utilisez-vous ?

0 votes

J'utilise gcc ! dans ubantu.

1 votes

Pourquoi faire cela, au lieu de la méthode normale "k = (int *) calloc (n, sizeof (int)) ;"? Juste pour obscurcir votre code ?

73voto

AndreyT Points 139512

Il ne s'agit pas d'une "allocation de mémoire statique". Votre tableau k est un tableau de longueur variable (VLA), ce qui signifie que la mémoire de ce tableau est allouée au moment de l'exécution. La taille sera déterminée par la valeur d'exécution de la variable n .

La spécification du langage n'impose pas de mécanisme d'allocation spécifique, mais dans une implémentation typique, votre fichier k se terminera généralement par un simple int * avec le bloc de mémoire réel alloué sur la pile au moment de l'exécution.

Pour un VLA sizeof est également évalué au moment de l'exécution, c'est pourquoi vous obtenez la valeur correcte dans votre expérience. Utilisez simplement %zu (pas %ld ) pour imprimer les valeurs de type size_t .

Le but premier de malloc (et d'autres fonctions d'allocation dynamique de mémoire) est de passer outre les règles de durée de vie basées sur la portée, qui s'appliquent aux objets locaux. C'est-à-dire que la mémoire allouée avec malloc reste alloué "pour toujours", ou jusqu'à ce que vous le désallouiez explicitement avec la commande free . Mémoire allouée avec malloc n'est pas automatiquement désalloué à la fin du bloc.

Le VLA, comme dans votre exemple, n'offre pas cette fonctionnalité de "limitation de la portée". Votre tableau k obéit toujours aux règles de durée de vie habituelles basées sur la portée : sa durée de vie se termine à la fin du bloc. Pour cette raison, dans le cas général, VLA ne peut pas remplacer malloc et d'autres fonctions d'allocation dynamique de mémoire.

Mais dans des cas spécifiques, lorsque vous n'avez pas besoin de "vaincre la portée" et que vous utilisez simplement malloc pour allouer un tableau de taille d'exécution, VLA pourrait en effet être considéré comme un remplacement de malloc . N'oubliez pas, une fois encore, que les VLA sont généralement alloués sur la pile et que l'allocation de gros morceaux de mémoire sur la pile reste à ce jour une pratique de programmation plutôt discutable.

0 votes

Et si j'utilisais static Eg- static int k[n] alors c'est comme malloc() ?

6 votes

@Rahul : C ne supporte pas static VLAs. Si n est une valeur d'exécution, alors static int k[n] n'est pas autorisé. Mais même si c'était autorisé, cela ne permettrait pas d'allouer un nouveau bloc de mémoire à chaque fois. En attendant. malloc alloue un nouveau bloc à chaque fois que vous l'appelez. Ainsi, il n'y a aucune similitude avec malloc ici même avec static .

2 votes

La formulation dans "dans une implémentation typique, votre k finira par être un simple int * pointeur" semble un peu risquée. Il y a déjà beaucoup de confusion sur les pointeurs et les tableaux.

11voto

Peter Points 4026

En C, le moyen par lequel un compilateur prend en charge les VLA (variables length arrays) dépend du compilateur - il n'est pas obligé d'utiliser malloc() et peut (et le fait souvent) utiliser ce que l'on appelle parfois la mémoire "pile" - par exemple en utilisant des fonctions spécifiques au système telles que alloca() qui ne font pas partie du langage C standard. S'il utilise la pile, la taille maximale d'un tableau est généralement beaucoup plus petite que ce qui est possible avec la fonction malloc() En effet, les systèmes d'exploitation modernes accordent aux programmes un quota de mémoire de pile beaucoup plus faible.

2 votes

Les systèmes d'exploitation modernes (et même les anciens systèmes d'exploitation embarqués dans certains cas) permettent à l'utilisateur de configurer la taille de la pile.

10voto

plugwash Points 795

La mémoire pour les tableaux de longueur variable ne peut clairement pas être allouée de manière statique. Elle peut cependant être allouée sur la pile. En général, cela implique l'utilisation d'un "pointeur de cadre" pour garder la trace de l'emplacement du cadre de la pile des fonctions face aux changements dynamiques du pointeur de la pile.

Lorsque j'essaie de compiler votre programme, il semble que ce qui se passe réellement est que le tableau de longueur variable a été optimisé. J'ai donc modifié votre code pour forcer le compilateur à allouer réellement le tableau.

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%s %ld",k,sizeof(k));
    return 0;
}

Godbolt compilant pour arm en utilisant gcc 6.3 (en utilisant arm parce que je peux lire les ASM d'arm) compile ceci à https://godbolt.org/g/5ZnHfa . (les commentaires sont de moi)

main:
        push    {fp, lr}      ; Save fp and lr on the stack
        add     fp, sp, #4    ; Create a "frame pointer" so we know where
                              ; our stack frame is even after applying a 
                              ; dynamic offset to the stack pointer.
        sub     sp, sp, #8    ; allocate 8 bytes on the stack (8 rather
                              ; than 4 due to ABI alignment
                              ; requirements)
        sub     r1, fp, #8    ; load r1 with a pointer to n
        ldr     r0, .L3       ; load pointer to format string for scanf
                              ; into r0
        bl      scanf         ; call scanf (arguments in r0 and r1)
        ldr     r2, [fp, #-8] ; load r2 with value of n
        ldr     r0, .L3+4     ; load pointer to format string for printf
                              ; into r0
        lsl     r2, r2, #2    ; multiply n by 4
        add     r3, r2, #10   ; add 10 to n*4 (not sure why it used 10,
                              ; 7 would seem sufficient)
        bic     r3, r3, #7    ; and clear the low bits so it is a
                              ; multiple of 8 (stack alignment again) 
        sub     sp, sp, r3    ; actually allocate the dynamic array on
                              ; the stack
        mov     r1, sp        ; store a pointer to the dynamic size array
                              ; in r1
        bl      printf        ; call printf (arguments in r0, r1 and r2)
        mov     r0, #0        ; set r0 to 0
        sub     sp, fp, #4    ; use the frame pointer to restore the
                              ; stack pointer
        pop     {fp, lr}      ; restore fp and lr
        bx      lr            ; return to the caller (return value in r0)
.L3:
        .word   .LC0
        .word   .LC1
.LC0:
        .ascii  "%d\000"
.LC1:
        .ascii  "%s %ld\000"

3voto

Mats Petersson Points 70074

La mémoire de cette construction, appelée "tableau à longueur variable" (VLA), est allouée sur la pile, de la même manière que dans le cas de alloca . La manière exacte dont cela se passe dépend du compilateur que vous utilisez, mais il s'agit essentiellement de calculer la taille lorsqu'elle est connue, puis de soustraire [1] la taille totale du pointeur de pile.

Vous avez besoin malloc et amis, car cette allocation "meurt" lorsque vous quittez la fonction. [Et ce n'est pas valable en C++ standard].

[1] Pour les processeurs typiques qui utilisent une pile qui "croît vers zéro".

0 votes

En C++, comme vous le savez certainement, vous utilisez std::vector pour un tableau dont vous pouvez déterminer la taille au moment de l'exécution, et dont la durée de vie est la portée actuelle. Vous pouvez également utiliser std::vector pour de nombreux autres usages. Il existe donc un substitut proche.

0 votes

@Davislor bien que ce ne soit pas sur la pile.

0 votes

@lalala std::vector vous permet de spécifier le std::allocator qu'il utilisera. IIRC, bien que je ne pense pas que la norme vous donne jamais une garantie que quelque chose utilise une pile. Mais si vous voulez vraiment un std::vector qui appelle alloca() vous pouvez en obtenir un. (Je suis presque sûr qu'une implémentation pourrait même écrire son runtime C en C++ et implémenter les VLAs C avec une interface C++. std::vector sous le capot !

0voto

Linkon Points 443

Quand on dit que le compilateur alloue de la mémoire pour les variables à temps de compilation Cela signifie que l'emplacement de ces variables est décidé et intégré dans le code exécutable que le compilateur génère, et non que le compilateur met à disposition l'espace nécessaire pour ces variables pendant qu'il travaille. L'allocation dynamique réelle de la mémoire est effectuée par le programme généré lors de son exécution.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X