C,C ++和Java中的提升/重新排序:变量声明必须始终位于上下文中吗?

我读了一些关于提升和重新排序的内容 ,因此似乎Java VM可能会选择提升某些表达式。 我还读到了在Javascript中提升函数声明的问题。

第一个问题:有人可以确认在C,C ++和Java中是否通常存在吊装? 或者它们都依赖于编译器/优化?

我读了很多示例C代码,它们总是在任何断言边界条件之前将变量声明置于顶部。 我认为在变量声明之前执行所有断言边界情况会更快一些,因为函数可能只是终止。

主要问题:变量声明必须始终在上下文中排在最前面吗? (这里有提升工作吗?)或者编译器是否通过首先检查这些独立的断言边界情况 (在无关变量声明之前)自动优化代码?

这是一个相关的例子:

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); } 

如上所示,边界情况不依赖于变量“a”和“b”。 因此,将边界情况置于变量声明之上会使它稍快一些吗?


更新

上面的例子并不像我希望的那样好,因为变量“a”和“b”只是声明,而不是在那里初始化。 在我们确实需要使用它们之前,编译器会忽略声明。

我使用初始化检查了GNU GCC程序集的变量声明,程序集具有不同的执行顺序。 编译器没有改变我对独立断言边界情况的排序。 因此,重新排序这些断言边界情况会改变程序集,从而改变机器运行它们的方式。

我认为差异是微不足道的,大多数人从不关心这一点。

编译器可以按照自己的意愿重新排序/修改代码,只要修改后的代码与原始代码顺序执行即可。 因此允许吊装,但不是必需的。 这是一个优化,它完全是编译器特定的。

C ++中的变量声明可以在任何地方。 在C语言中他们过去必须在上下文中处于领先地位,但是当引入c99标准时,规则被放宽了,现在它们可以在任何你想要的地方,类似于c ++。 尽管如此,许多c程序员仍坚持将它们放在上下文中。

在您的示例中,编译器可以自由地将if语句移动到顶部,但我认为不会。 这些变量只是在堆栈上声明并且未初始化的指针,声明它们的成本很小,而且在函数开头创建它们可能更有效,而不是在断言之后。

例如,如果您的声明涉及任何副作用

 struct node *a = some_function(); 

然后编译器将受限于它可以重新排序。

编辑:

我通过这个简短的程序在实践中检查了GCC的循环提升:

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

我用这个命令编译了它:

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

这是输出:

  .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 

然后我用这个命令编译它:

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

这是输出:

  .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 

所以基本上,在启用优化的情况下,原始代码转换为如下所示:

 #include  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; } 

所以,正如你所看到的,确实在C中悬挂。

C,C ++,Java中不存在提升。

变量声明可以在C ++和Java的方法或函数中的任何位置发生,但必须在使用该值之前。 对于C,它必须位于顶部。

这些语言中的变量范围可以是全局的,也可以是花括号使用的任何地方(因此您可以任意将一对花括号投入C程序并引入新的变量范围 – 在Javascript中,您可以使用闭包实现相同的function)