Considérons l'exemple ci-dessous
void func(int i){
if(i) {
int arr[2048] = {0};
//Doing something related to arr;
} else {
//Doing something
}
}
J'ai une déclaration de grand tableau dans le bloc if . L'initialisation de ce tableau devrait prendre du temps. Ma question est: si i == 0 , ce tableau sera-t-il vraiment initialisé?
3 Réponses :
si i == 0, ce tableau sera-t-il initialisé?
parce que votre code est
if(i) { int arr[2048] = {0}; //Doing something related to arr; } else { //Doing something }le tableau n'existe pas si
i == 0donc il ne peut pas être initialisé, le tableau n'existe que dans la branche du if oùi! = 0
Ce n'est peut-être pas le cas. Un compilateur peut collecter toutes les variables une fois et les allouer (et les initialiser) une fois. Un autre compilateur peut réutiliser l'espace de pile. En tout, si le tableau "existe" n'est pas un problème de langage mais un problème de compilation.
De même, je pense qu'un compilateur peut optimiser le code et ne pas déclarer de tableau même lorsque i = 0. Cela dépend de la façon dont le tableau est utilisé.
Quelle que soit la façon dont l'allocation de arr est implémentée, tout compilateur sensé n'appellerait pas l'initialisation si i est faux.
Pour comprendre le comportement du compilateur, vous devez considérer que dans le langage C, chaque variable a une classe de stockage ( ISO / CEI 9899: 201x §6.2.4 Durées de stockage des objets strong>) qui caractérisent son comportement et sa ' vie ', c'est-à-dire l'existence d'un tel objet (une variable est un objet), et les conditions légales d'accès à celui-ci.
Les classes de stockage sont 4: statique, thread, automatique et allouée. Ces derniers utilisent l'allocation de mémoire dynamique. Dans votre cas, le tableau Pour un tel objet qui n'a pas de type tableau de longueur variable,
sa durée de vie s'étend de l'entrée dans le bloc avec lequel il est
associé jusqu'à ce que l'exécution de ce bloc se termine de quelque manière que ce soit. (Saisie d'un
le bloc fermé ou l'appel d'une fonction suspend, mais ne se termine pas,
exécution du bloc courant.) Si le bloc est saisi récursivement, une nouvelle instance de l'objet est
créé à chaque fois. La valeur initiale de l'objet est indéterminée . Si une initialisation est spécifiée pour l'objet, elle est effectuée chaque
heure à laquelle la déclaration ou le littéral composé est atteint lors de l'exécution
du bloc ; sinon, la valeur devient indéterminée chaque fois que le
la déclaration est atteinte. Ceci explique que: Le premier point est clair, et c'est déjà la réponse à votre question. Le code auquel vous faites référence est: Si vous n'entrez pas dans le bloc, la vie de votre objet, le tableau, ne commence pas: le tableau n'existe pas. Bien entendu dans cette condition aucune opération ne peut être effectuée sur l'objet, même l'initialisation. Désormais, le point 2 permet de mieux clarifier. L'expression: N'est pas interprétée fonctionnellement par le compilateur comme une déclaration avec initialisation d'objet , à cause de la classe de stockage de l'objet tableau. Elle est, en grande partie, traitée comme une déclaration plus une affectation . Quelle est la différence? Une déclaration d'une variable initialisée ayant une classe de stockage différente de automatic est implémenté avec un mécanisme transparent au code utilisateur, attribuant statiquement des valeurs dans la section BSS ou avec du code appartenant aux prologues et épilogues du compilateur, cela pourrait se produire même si l'objet n'est pas accédé . p> Dans l'autre cas, le code d'initialisation, l'affectation, fait partie du code utilisateur , et pour cette raison exécuté en suivant la logique du flux d'exécution . p> Ceci est le comportement officiel . En inspectant sous le capot, vous pouvez voir que dans certains cas, l'espace pour les variables automatiques est alloué à l'avance depuis le début de la vie de l'objet, mais ces comportements dépendent strictement de l'architecture du processeur, et fondamentalement, lorsque cela se produit, ne crée pas de divergence fonctionnelle le code le standard du langage (qui est la propriété de base d'un compilateur compatible ). arr [2048] est un objet automatique , dont la durée de vie est définie (dans le même paragraphe de la norme @ point 6) comme:
int arr[2048] = {0};
{ //Block init
int arr[2048] = {0};
//Doing something related to arr;
} // block end
En pratique, une variable sera initialisée avant utilisation , qu'elle soit placée dans une portée interne ou non. Ce code:
.LC0:
.string "%d"
func2:
test edi, edi
jne .L7
ret
.L7:
xor esi, esi
mov edi, OFFSET FLAT:.LC0
xor eax, eax
jmp printf
donne exactement le même code machine que ce code:
.LC0:
.string "%d"
func1:
test edi, edi
jne .L4
ret
.L4:
xor esi, esi
mov edi, OFFSET FLAT:.LC0
xor eax, eax
jmp printf
gcc -O3 sur x86 donne:
void func2 (int i){
int arr[2048] = {0};
if(i) {
printf("%d", arr[666]);
} else {
//Doing something
}
}
et
void func1 (int i){
if(i) {
int arr[2048] = {0};
printf("%d", arr[666]);
} else {
//Doing something
}
}
Comme vous pouvez le voir, ils sont identiques.
C'est une bonne pratique de conception de limiter autant que possible la portée des variables, mais cela n'a rien à voir avec les performances.
Sur quel compilateur, en utilisant quelles options de compilation sur quelle architecture? Une compilation peut générer une instruction d'assemblage nécessaire pour initialiser le tableau, contrairement à l'autre compilateur. Par exemple, on peut voir que sur gcc 8.3 sans options sur la plateforme x64 le
arrest initialisé uniquement siiest différent de zéro, voir ici .D'après ce godbolt.org/z/SCj1g3 , l'initialisation n'est pas effectuée lorsque
i < / code> vaut0, dans ce cas, il saute juste au-dessus de l'initialisation. Bien sûr, cela dépendra du compilateur.En C89, ce code conduirait à un message d'erreur, indiquant que toutes les variables doivent être déclarées au début d'une fonction ou au moins de la portée - selon le compilateur.
@ SvenKrüger ce code ne provoquera pas d'erreur en C89 à cause de arr ... mais à cause du '//'
@ SvenKrüge
int arr [2048] = {0};dans le code ci-dessus ne viole pas cette règle.@ SvenKrüger
arrest défini au début de la portée. Où voyez-vous un problème? La définition de nouvelles variables au début d'un ensemble interne de{...}était également autorisée dans C89. Cela ne devrait pas dépendre du compilateur