我目前正在学习计算机科学并想了解所有内容,所以我在书中注意到人们声明了一个变量int x;
,然后在声明的正下方初始化它,例如
int x;
x = 0;
我想知道这是否对内存或编译代码的效率有任何好处?
我目前正在学习计算机科学并想了解所有内容,所以我在书中注意到人们声明了一个变量int x;
,然后在声明的正下方初始化它,例如
int x;
x = 0;
我想知道这是否对内存或编译代码的效率有任何好处?
我目前正在学习计算机科学并想了解一切
你来对地方了!
如果这对内存或编译代码的效率有好处
它没有。但是你怎么知道呢?经验数据,兄弟!
花点时间研究一下我的编译器的这两个中间输出。这里有一个传说:最左边的那一列没意思,忽略它。下一列显示源文件名的名称 ( example2.c
) 或从源生成的可执行机器指令 ( 0000 55
)。原始来源中的行显示在您看到四个星号的位置。它与编译时生成的相应程序集输出交错。指令助记符和参数显示在可执行指令的右侧。在两个示例之间来回查看,您可以看到两个示例中的说明是相同的。
我使用gcc
( gcc -c -g -Wa,-ahl=example.s example.c
) 创建了这些示例。
首先,使用“健全”的初始化:
6 with_init:
7 .LFB0:
8 .file 1 "example2.c"
1:example2.c ****
2:example2.c **** int with_init()
3:example2.c **** {
9 .loc 1 3 0
10 .cfi_startproc
11 0000 55 pushq %rbp
12 .LCFI0:
13 .cfi_def_cfa_offset 16
14 .cfi_offset 6, -16
15 0001 4889E5 movq %rsp, %rbp
16 .LCFI1:
17 .cfi_def_cfa_register 6
4:example2.c **** int x = 0;
18 .loc 1 4 0
19 0004 C745FC00 movl $0, -4(%rbp)
19 000000
5:example2.c ****
6:example2.c **** return x;
20 .loc 1 6 0
21 000b 8B45FC movl -4(%rbp), %eax
7:example2.c **** }
22 .loc 1 7 0
23 000e 5D popq %rbp
24 .LCFI2:
25 .cfi_def_cfa 7, 8
26 000f C3 ret
现在有了你提出的更“有趣”的案例:
6 later_init:
7 .LFB0:
8 .file 1 "example.c"
1:example.c ****
2:example.c **** int later_init()
3:example.c **** {
9 .loc 1 3 0
10 .cfi_startproc
11 0000 55 pushq %rbp
12 .LCFI0:
13 .cfi_def_cfa_offset 16
14 .cfi_offset 6, -16
15 0001 4889E5 movq %rsp, %rbp
16 .LCFI1:
17 .cfi_def_cfa_register 6
4:example.c **** int x;
5:example.c ****
6:example.c **** x = 0;
18 .loc 1 6 0
19 0004 C745FC00 movl $0, -4(%rbp)
19 000000
7:example.c ****
8:example.c **** return x;
20 .loc 1 8 0
21 000b 8B45FC movl -4(%rbp), %eax
9:example.c **** }
22 .loc 1 9 0
23 000e 5D popq %rbp
24 .LCFI2:
25 .cfi_def_cfa 7, 8
26 000f C3 ret
没有不同!
编辑:我之前没有看到java
标签。在这种情况下可以说更直接:
$ cat example.java
class SOComparison
{
public static int with_init()
{
int x = 0;
return x;
}
public static int later_init()
{
int x;
x = 0;
return x;
}
}
$ javap -c SOComparison
Compiled from "example.java"
class SOComparison {
SOComparison();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static int with_init();
Code:
0: iconst_0
1: istore_0
2: iload_0
3: ireturn
public static int later_init();
Code:
0: iconst_0
1: istore_0
2: iload_0
3: ireturn
}
除了代码可读性之外,没有任何变化:您的编译器应该确定第一个赋值发生在其他地方,并在那里执行赋值。
通常,您应该更喜欢将初始化和赋值结合起来,除非第一次赋值应该发生在内部范围内而不是声明变量的范围内,例如在do
/while
循环内分配的变量,并在循环完成后使用:
bool found; // No assignment
do {
found = false;
...
if (...) {
found = true;
}
...
} while (!finished(someCondition));
除非您有一个非常不寻常的编译器,否则这不应更改已编译代码中的任何内容。
这是Java的故事:
对于简单的类:
public class MyClass {
public static void main(String... args)
{
int x = 0;
}
}
和
public class MyClass {
public static void main(String... args)
{
int x;
x = 0;
}
}
在这两种情况下我们得到相同的字节码:
因此,由于两者之间没有区别。作为参考,这是通过以下方式生成的:
javap -c MyClass.class
通常,如果您的变量与声明的范围相同,则不会有性能(甚至代码)差异。如今,任何现代编译器都应该能够进行这种简单的优化。(如果不把它扔掉并得到一个像样的。)
如果您在定义范围内进行任何赋值之前将变量的地址传递给函数并在那里对其进行初始化,那么它可以产生最小的差异。但是您确实需要良好且执行的代码来注意这一点,因此您应该首先关注可读性和安全性(永远不要使用单元化内存),并且只有在您测量了与该初始化有关的合理性能问题时才会回到这一点。
完全不在汇编语言范围内。在这里查看http://gcc.godbolt.org/