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()
?
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.
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.
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"
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".
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.