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"
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 ?
30 votes
@jamesqf Comment est
int k[n];
une version obfusquée dek = (int *) calloc (n, sizeof (int));
? Je pense que le premier est plus lisible (si vous savez que les VLAs existent).5 votes
@jamesqf : performance. Avec
n
chargé dansrsi
(prêt à être le 2ème arg de printf dans l'ABI SysV x86-64),sub rsp, rsi
(une simple instruction asm) est beaucoup moins cher qu'un appel de fonction àcalloc
. Bien que dans ce cas,k[]
lui-même n'est pas utilisé, seulementsizeof(k)
donc un bon compilateur ne prendra pas la peine de réserver de l'espace de pile avant d'appelerprintf
. La mémoire de pile est déjà chaude dans le cache L1D et le TLB, c'est donc un bon endroit pour les petits buffers. Il est également extrêmement bon marché de la libérer, et vous ne pouvez pas vous tromper car le compilateur le fait pour vous.0 votes
@Arcinde : Obfusqué parce que (comme la plupart des choses en C obfusqué) c'est quelque chose que je pense que beaucoup, sinon la plupart des programmeurs expérimentés a) ne sauraient pas qu'il existe ; et b) ne croiraient pas que cela fonctionnerait réellement s'ils le voyaient. Ce n'est pas le cas pour moi.
0 votes
@Peter Cordes : Comment le compilateur peut-il savoir qu'il doit réserver de l'espace de pile, alors que la taille de l'espace nécessaire n'est pas connue avant l'exécution ? Je peux voir que cela pourrait fonctionner pour les petits tableaux, mais qu'est-ce qui définit "petit" ou limite n à être "petit" ? S'il n'y a pas assez de pile pour un grand n, comment le programme peut-il échouer de manière gracieuse ?
0 votes
@o_weisman : C'est en C, et non en C++, que les compilateurs peuvent prendre en charge cette fonctionnalité de manière facultative, conformément à la norme C11.
3 votes
@jamesqf : Il ne vérifie pas la taille, et il n'échoue pas de manière gracieuse. C'est au programmeur de ne pas écrire de programmes qui utilisent une VLA trop grande pour les implémentations sur lesquelles ils veulent fonctionner. (par exemple Taille de la pile de 8 Mo dans les nouveaux threads de l'espace utilisateur sous Linux x86-64 ). En général, vous faites une ségrégation si vous touchez de la mémoire en dessous du bas de la pile et que le système d'exploitation décide que c'est trop et n'augmente pas votre mappage de pile. C'est une mauvaise idée d'utiliser un grand VLA dans une fonction non-feuille avec des enfants qui pourraient également utiliser des VLA.
1 votes
@jamesqf : On dirait que c'est beaucoup plus grave que
new
/delete
Mais avec les systèmes d'exploitation modernes qui surcommencent la mémoire, c'est à peine pire. Vous pouvez allouer beaucoup plus de RAM que le système d'exploitation n'en a de RAM physique + espace d'échange, et toucher à tout peut amener le noyau à décider de tuer votre processus. (Linux appelle cela le tueur d'OOM). linuxdevcenter.com/pub/a/linux/2006/11/30/ . Vous pouvez faire en sorte que l'allocation échoue gracieusement en fixant des limites à la quantité de mémoire virtuelle qu'un processus peut allouer.malloc
retournera en fait NULL, mais ce n'est pas le cas par défaut.0 votes
Le code affiché par le PO utilise des VLA (Variable Length Arrays). Il ne s'agit PAS d'une allocation de mémoire à la compilation, mais plutôt d'une allocation de mémoire à l'exécution. La fonctionnalité VLA est un ajout récent au langage.