在C,C ++和Java中的提升/重新排序:必须变量声明总是在上下文中顶部? [英] Hoisting/Reordering in C, C++ and Java: Must variable declarations always be on top in a context?

查看:222
本文介绍了在C,C ++和Java中的提升/重新排序:必须变量声明总是在上下文中顶部?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我阅读了一些关于提升和重新排序的信息,因此似乎Java VM可能选择提升一些表达式。

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



我读了很多示例C代码,总是把变量声明放在顶部,在任何 assert 边界条件。我认为在给定函数可以终止的变量声明之前执行所有的声明边界情况会更快一些。



主要问题:
必须变量声明始终位于上下文中吗? (这里是否有工作在这里工作?)或者编译器是否通过首先检查这些独立的断言边界实例来自动优化代码? >

这是一个相关的例子:

  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 ++。



在你的示例中,编译器可以自由地将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\\\
,i ++);
return 0;
}

我用这个命令编译:

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

这是输出:

  .filetest.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 b $ b 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:
.identGCC:(GNU)4.8.2
.def _printf; .scl 2; .type 32; .endef

然后我用这个命令编译:

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

这是输出:

  .filetest.c
.def ___main; .scl 2; .type 32; .endef
.section .rdata,dr
LC0:
.ascii%d\12\0
.section .text.startup,x
.p2align 4,and 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,and 7
L12:
movl%ebx,%eax
L8:
leal 1 (%eax),%ebx
movl%eax,4(%esp)
movl $ LC0(%esp)
call _printf
cmpl $ 11,%ebx $ b b 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:
.identGCC:(GNU)4.8.2
.def _printf; .scl 2; .type 32; .endef

所以基本上,优化打开原来的代码转换成这样: p>

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

所以,你可以看到, p>

I read a little about hoisting and reordering, so it seems that Java VM may choose to hoist some expressions. I also read about hoisting of function declarations in Javascript.

First Question: Can someone confirm if hoisting usually exist in C, C++ and Java? or are they all compiler/optimization dependent?

I read a lot of example C codes that always put variable declarations on top, before any assert or boundary condition. I thought it would be a little faster to do all the asserts and boundary cases before variable declarations given that the function could just terminate.

Main Question: Must variable declarations always be on top in a context? (is there hoisting at work here?) Or does the compiler automatically optimize the code by checking these independent asserts and boundary cases first (before irrelevant variable declaration)?

Here's a related example:

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

As shown above, the boundary case does not depend on variables "a" and "b". Thus, putting the boundary case above variable declarations would make it slightly faster?


Updates:

The above example isn't as good as I hoped because variables "a" and "b" were only declared, not initialized there. Compiler would ignore declaration until we actually need to use them.

I checked GNU GCC assemblies for variable declarations with initializations, the assemblies have different execution sequence. Compiler did not change my ordering of independent asserts and boundary cases. So, reordering these asserts and boundary cases do change the assemblies, thus changing how machine runs them.

I suppose the difference is minuscule that most people never cared about this.

解决方案

The compiler may reorder/modify your code as it wishes, as long as the modified code is equivalent to the original if executed sequentially. So hoisting is allowed, but not required. This is an optimization and it is completely compiler specific.

Variable declarations in C++ can be wherever you wish. In C they used to have to be on top in a context, but when the c99 standard was introduced, the rules were relaxed and now they can be wherever you want, similarly to c++. Still, many c programmers stick to putting them on top in a context.

In your example, the compiler is free to move the if statements to the top, but I don't think it would. These variables are just pointers that are declared on stack and are un-initialized, the cost of declaring them is minimal, moreover it might be more efficient to create them at the beginning of the function, rather than after the asserts.

If your declarations would involve any side-effects, for example

struct node *a = some_function();

then compiler would be limited in what it can reorder.

Edit:

I checked GCC's loop hoisting in practice with this short program:

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

I've compiled it with this command:

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

This is the output:

    .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

Then I've compiled it with this command:

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

This is the output:

    .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

So basically, with optimization turned on the original code was transformed to something like this:

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

So, as you can see, there is indeed hoisting in C.

这篇关于在C,C ++和Java中的提升/重新排序:必须变量声明总是在上下文中顶部?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆