C、C++和Java中的HoistingReordering。变量声明在上下文中必须总是在上面吗?

问题描述 投票:6回答:3

我读了一点关于 拔苗助长因此,Java虚拟机似乎可以选择箍住一些表达式。 我也读到了关于Javascript中函数声明的hoisting。

第一个问题。 谁能确认一下,在C、C++和Java中,是否通常都有hoisting? 还是它们都取决于编译器的优化?

我读了很多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程序集的变量声明与初始化,这些程序集有不同的执行顺序。编译器没有改变我对独立变量的排序。声称边界案例. 所以,重新排序这些 声称边界案例 做改变装配,从而改变机器的运行方式。

我想这两者之间的差别微乎其微,以至于大多数人从不关心这个问题。

java javascript c++ c optimization
3个回答
5
投票

编译器可以按照自己的意愿重新排序修改你的代码,只要修改后的代码与原代码等价,如果按顺序执行即可。所以葫芦是允许的,但不是必须的。这是一种优化,它完全是编译器特有的。

在C++中,变量声明可以随心所欲。在C中,以前它们必须在上下文中顶着,但当c99标准出台后,规则就放宽了,现在它们可以在任何你想在的地方,这和c++类似。不过,很多c程序员还是坚持把它们放在上下文中的顶部。

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

如果你的声明会涉及到任何副作用,例如

struct node *a = some_function();

那么编译器能重新排序的内容就会受到限制。

编辑。

我用这个小程序检查了GCC的循环提升功能的实际效果

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

我用这个命令编译了它:

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

所以,正如你所看到的,在C语言中确实有葫芦。


0
投票

其实java中也有葫芦的概念。的代码。

while (!stop)

        i++;

可能会转化成这样的代码

if (!stop)

    while (true)

        i++;

当给定的方法上没有同步块时,JVM确实(允许)进行这种 "优化"。

更多细节可以参考Effective Java, 3rd Edition , 第11章,并发性


-1
投票

C、C++、Java中不存在Hoisting。

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

在这些语言中,变量作用域要么是全局的,要么是在使用大括号的地方(所以你可以任意在C程序中扔一对大括号,然后引入一个新的变量作用域--在Javascript中,你会使用闭包来实现同样的事情)。

© www.soinside.com 2019 - 2024. All rights reserved.