Levage / Réorganisation en C, C ++ et Java: les déclarations de variable doivent-elles être toujours en tête dans un contexte?

J'ai lu un peu sur le levage et la réorganisation , de sorte qu'il semble que Java VM puisse choisir d'hisser des expressions. J'ai également lu sur le levage des déclarations de fonction en Javascript.

Première question: Quelqu'un peut-il confirmer si le levage existe habituellement en C, C ++ et Java? Ou sont-ils tous dépendants du compilateur / optimisation?

J'ai lu beaucoup d'exemples de codes C qui mettent toujours des déclarations variables en haut, avant toute condition d' affirmation ou de limite . Je pensais qu'il serait un peu plus rapide de faire toutes les affirmations et les cas limites avant les déclarations variables étant donné que la fonction pourrait simplement se terminer.

Question principale: Les déclarations variables doivent-elles être toujours en tête dans un contexte? (Y a-t-il hissé au travail ici?) Ou le compilateur optimise-t-il automatiquement le code en vérifiant d'abord ces affirmations indépendantes et les premières limites (avant déclaration de variable non pertinente)?

Voici un exemple connexe:

void MergeSort(struct node** headRef) { struct node* a; struct node* b; if ((*headRef == NULL) || ((*headRef)->next == NULL)) { return; } FrontBackSplit(*headRef, &a, &b); MergeSort(&a); MergeSort(&b); *headRef = SortedMerge(a, b); } 

Comme indiqué ci-dessus, le cas limite ne dépend pas des variables "a" et "b". Ainsi, mettre le cas limite au-dessus des déclarations variables le rendrait-il légèrement plus rapide?


Mises à jour :

L'exemple ci-dessus n'est pas aussi bon que j'espérais parce que les variables "a" et "b" n'étaient déclarées, non initialisées là-bas. Le compilateur ignorait la déclaration jusqu'à ce qu'il soit nécessaire de les utiliser.

J'ai vérifié les assemblages GNU GCC pour les déclarations variables avec des initialisations, les assemblages ont une séquence d'exécution différente. Le compilateur n'a pas changé ma commande d'affirmations indépendantes et de cas limites . Donc, en réordonnant ces affirmations et les cas limites, modifiez les assemblages, changeant ainsi la manière dont la machine les exécute.

Je suppose que la différence est minime que la plupart des gens n'ont jamais aimé cela.

Le compilateur peut réorganiser ou modifier votre code tel qu'il le souhaite, tant que le code modifié est équivalent à l'original s'il est exécuté séquentiellement. Le levage est donc autorisé, mais pas nécessaire. Il s'agit d'une optimisation et il est complètement spécifique au compilateur.

Les déclarations de variables en C ++ peuvent être là où vous le souhaitez. En C, ils devaient être sur le dessus dans un contexte, mais lorsque la norme c99 a été introduite, les règles étaient assouplies et maintenant elles peuvent être là où vous voulez, de même que C ++. Néanmoins, de nombreux programmateurs c continuent de les mettre au top dans un contexte.

Dans votre exemple, le compilateur est libre de déplacer les instructions if vers le haut, mais je ne le pense pas. Ces variables ne sont que des pointeurs qui sont déclarés sur la pile et ne sont pas initialisés, le coût de les déclarer est minimal, de plus il pourrait être plus efficace de les créer au début de la fonction plutôt que après les affirmations.

Si vos déclarations impliquent des effets secondaires, par exemple

 struct node *a = some_function(); 

Alors le compilateur serait limité dans ce qu'il peut réorganiser.

Modifier:

J'ai vérifié le levage de la boucle de GCC en pratique avec ce programme court:

 #include <stdio.h> int main(int argc, char **argv) { int dummy = 2 * argc; int i = 1; while (i<=10 && dummy != 4) printf("%d\n", i++); return 0; } 

Je l'ai compilé avec cette commande:

 gcc -std=c99 -pedantic test.c -S -o test.asm 

C'est la sortie:

  .file "test.c" .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC0: .ascii "%d\12\0" .text .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB7: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp call ___main movl 8(%ebp), %eax addl %eax, %eax movl %eax, 24(%esp) movl $1, 28(%esp) jmp L2 L4: movl 28(%esp), %eax leal 1(%eax), %edx movl %edx, 28(%esp) movl %eax, 4(%esp) movl $LC0, (%esp) call _printf L2: cmpl $10, 28(%esp) jg L3 cmpl $4, 24(%esp) jne L4 L3: movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE7: .ident "GCC: (GNU) 4.8.2" .def _printf; .scl 2; .type 32; .endef 

Ensuite, je l'ai compilé avec cette commande:

 gcc -std=c99 -pedantic test.c -O3 -S -o test.asm 

C'est la sortie:

  .file "test.c" .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC0: .ascii "%d\12\0" .section .text.startup,"x" .p2align 4,,15 .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB7: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 pushl %ebx andl $-16, %esp subl $16, %esp .cfi_offset 3, -12 call ___main movl 8(%ebp), %eax leal (%eax,%eax), %edx movl $1, %eax cmpl $4, %edx jne L8 jmp L6 .p2align 4,,7 L12: movl %ebx, %eax L8: leal 1(%eax), %ebx movl %eax, 4(%esp) movl $LC0, (%esp) call _printf cmpl $11, %ebx jne L12 L6: xorl %eax, %eax movl -4(%ebp), %ebx leave .cfi_restore 5 .cfi_restore 3 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE7: .ident "GCC: (GNU) 4.8.2" .def _printf; .scl 2; .type 32; .endef 

Donc, fondamentalement, avec l'optimisation activée, le code original a été transformé en quelque chose comme ceci:

 #include <stdio.h> int main(int argc, char **argv) { int dummy = 2 * argc; int i = 1; if (dummy != 4) while (i<=10) printf("%d\n", i++); return 0; } 

Donc, comme vous pouvez le voir, il y a effectivement un levage dans C.

La levage n'existe pas en C, C ++, Java.

La déclaration de variable peut se produire à n'importe quel point d'une méthode ou d'une fonction pour C ++ et Java, mais elle doit être avant que la valeur ne soit utilisée. Pour C, il doit être en haut.

La portée variable dans ces langues est soit globale, soit partout où les accolades sont utilisées (de sorte que vous pouvez arbitrairement jeter une paire d'accolades dans un programme C et introduire une nouvelle portée variable – en Javascript, vous obtiendrez la même chose à l'aide d'une fermeture)