web-dev-qa-db-fra.com

Comment le compilateur alloue-t-il de 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 taille d'un tableau entier, 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 taille requise.
Mais toute l’allocation de mémoire statique est faite au moment de la compilation et, pendant le temps de compilation, la valeur de n n’est pas connue. Comment se fait-il que le compilateur puisse allouer la mémoire de la taille requise?

Si nous pouvons allouer la mémoire requise de cette manière, quelle est l’utilisation de l’allocation dynamique en utilisant malloc() et calloc()?

66
Rahul

Ce n'est pas 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 n.

La spécification de langue ne dicte aucun mécanisme d'allocation spécifique, mais dans une implémentation typique, votre k finira généralement par être un simple pointeur int * avec le bloc de mémoire réel alloué sur la pile au moment de l'exécution. 

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

L'objectif principal de malloc (et d'autres fonctions d'allocation de mémoire dynamique) est de remplacer les règles de durée de vie basées sur la portée, qui s'appliquent aux objets locaux. C'est à dire. La mémoire allouée avec malloc reste allouée "pour toujours" ou jusqu'à ce que vous la libériez explicitement avec free. La mémoire allouée avec malloc ne sera pas automatiquement désallouée à la fin du bloc.

VLA, comme dans votre exemple, ne fournit pas cette fonctionnalité "qui va à l'encontre de la portée". Votre tableau k obéit toujours aux règles de durée de vie standard basées sur l'étendue: sa durée de vie se termine à la fin du bloc. Pour cette raison, en règle générale, VLA ne peut pas remplacer malloc et d'autres fonctions d'allocation de mémoire dynamique.

Mais dans des cas spécifiques où vous n'avez pas besoin de "vaincre la portée" et d'utiliser simplement malloc pour allouer un tableau de la taille d'une exécution, VLA peut en effet être considéré comme un remplacement de malloc. N'oubliez pas, encore une fois, 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 à ce jour reste une pratique de programmation plutôt discutable.

72
AnT

En C, les moyens par lesquels un compilateur prend en charge les VLA (tableaux de longueur variable) dépendent du compilateur - il n’est pas obligé d’utiliser malloc() et peut (et le fait souvent) utiliser ce que l’on appelle parfois une mémoire "empilée" - par exemple. utilisation de fonctions spécifiques au système, telles que alloca(), qui ne font pas partie de la norme C. S'il utilise pile, la taille maximale d'un tableau est généralement beaucoup plus petite que celle possible avec malloc(), car les systèmes d'exploitation modernes autorisent les programmes à disposer d'un quota de mémoire d'empilement beaucoup plus réduit.

11
Peter

La mémoire pour les tableaux de longueur variable ne peut clairement pas être allouée de manière statique. Il peut cependant être alloué sur la pile. Généralement, cela implique l’utilisation d’un "pointeur de trame" pour garder une trace de l’emplacement du cadre de pile de fonctions face aux modifications déterminées de manière dynamique du pointeur de 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 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 compiler pour arm en utilisant gcc 6.3 (en utilisant arm parce que je peux lire ASM) compile ceci en https://godbolt.org/g/5ZnHfa . (commente le mien)

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"
10
plugwash

La mémoire pour cette construction, appelée "tableau de longueur variable", VLA, est allouée sur la pile, de la même manière que alloca. Cela dépend exactement du compilateur que vous utilisez, mais il s’agit essentiellement de calculer la taille quand elle est connue, puis de soustraire [1] la taille totale du pointeur de pile.

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

[1] Pour les processeurs classiques utilisant une pile qui "atteint zéro".

3
Mats Petersson

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 généré par le compilateur, et non que le compilateur crée de l'espace. pour ceux-ci disponibles pendant que cela fonctionne ... L'allocation de mémoire dynamique réelle est effectuée par le programme généré lors de son exécution.

0
Linkon