C、c++和Java中的提升/重排序:必须变量声明总是在上下文的顶部
Hoisting/Reordering in C, C++ and Java: Must variable declarations always be on top in a context?
我读了一些关于提升和重新排序的内容,所以看起来Java VM可能会选择提升一些表达式。我还读到了Javascript中函数声明的提升。
第一个问题是:<<p> /strong>有人能确认在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"answers"b"只是在那里声明的,而不是在那里初始化的。编译器会忽略声明,直到我们真正需要使用它们。
我检查了GNU GCC程序集的变量声明与初始化,程序集有不同的执行顺序。编译器没有改变独立断言和边界情况的顺序。因此,重新排序这些断言和边界情况确实改变了程序集,从而改变了机器运行它们的方式。
编译器可以根据需要重新排序/修改代码,只要修改后的代码与按顺序执行的原始代码等效即可。所以吊装是允许的,但不是必须的。这是一个优化,它完全是编译器特定的。
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("%dn", 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 "%d12 "
.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 "%d12 "
.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("%dn", i++);
return 0;
}
所以,正如你所看到的,c中确实有提升。
实际上java中存在提升的概念。代码:
while (!stop)
i++;
可以转换成以下代码:
if (!stop)
while (true)
i++;
当给定方法上没有同步块时,JVM会执行(允许)这种"优化"。
更多的细节可以在Effective Java, 3rd Edition, chapter 11, concurrency中找到
在C、c++、Java中不存在提升。
变量声明可以在c++和Java的方法或函数中的任何地方进行,但必须在使用值之前进行。对于C,它必须在顶部。
这些语言中的变量作用域要么是全局的,要么是使用大括号的地方(因此您可以在C程序中任意地添加一对大括号并引入新的变量作用域——在Javascript中您可以使用闭包实现相同的目的)
- 在疯狂的部分中声明变量
- 如何在C++中为高分辨率时钟声明变量?
- 在命名空间中声明变量,在 main 中定义它,使其对所有其他文件可见
- CUDA 的性能取决于声明变量
- 如何在不为其声明变量的情况下获取和使用常量值的地址?
- C++声明变量时自动类型推断而不初始化
- 在不同循环中多次声明变量的优点
- 奇怪的错误 C2131 与 constexpr 声明变量
- 是否可以在 "if" 语句中声明变量?
- 在python-ctypes中声明变量并传递给dll函数
- 在递归函数C++中声明变量
- 只有一个定义/声明时标头声明变量的多堆定义错误
- 奇怪的未声明变量编译器错误
- 我在C++程序中声明变量时遇到问题
- 在命名空间中声明变量
- C++ lambda 按值捕获,而无需更早声明变量
- 声明变量以保存字符串列表时的内存分配
- 如何声明C 变量应突变
- 为什么允许在开关语句中声明变量?但不是声明 初始化
- 在同一命名空间中声明变量和函数是否出错?[C++]