每当您在C范围(例如函数)中分配局部变量时,它们就没有默认的初始化代码(例如C ++构造函数)。并且由于它们不是动态分配的(它们只是未初始化的指针),因此无需调用其他(可能是昂贵的)函数malloc
来准备/分配它们。
由于堆栈的工作方式,分配堆栈变量仅意味着递减堆栈指针(即增加堆栈大小,因为在大多数体系结构中,其向下增长),以便为其腾出空间。从CPU的角度来看,这意味着执行一条简单的SUB指令:(SUB rsp, 4
如果您的变量为4字节大-例如常规的32位整数)。
此外,当声明多个变量时,编译器足够聪明,可以将它们实际组合在一起成为一条大SUB rsp, XX
指令,这XX
是作用域局部变量的总大小。理论上。实际上,情况有所不同。
在这种情况下,当我发现(非常容易)发现编译器“幕后”发生了什么时,我发现GCC Explorer是一个非常有价值的工具。
因此,让我们看一下实际编写如下函数时会发生什么:GCC explorer link。
C代码
int function(int a, int b) {
int x, y, z, t;
if(a == 2) { return 15; }
x = 1;
y = 2;
z = 3;
t = 4;
return x + y + z + t + a + b;
}
结果组装
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
cmp DWORD PTR [rbp-20], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
-- snip --
.L3:
pop rbp
ret
事实证明,海湾合作委员会甚至比这更聪明。它甚至根本不执行SUB指令来分配局部变量。它只是(内部)假设该空间已“被占用”,但未添加任何指令来更新堆栈指针(例如SUB rsp, XX
)。这意味着堆栈指针不会保持最新,但由于在这种情况下,在使用堆栈空间后PUSH
不再执行任何指令(并且不会进行rsp
相对查找),因此没有问题。
这是一个没有声明其他变量的示例:http : //goo.gl/3TV4hE
C代码
int function(int a, int b) {
if(a == 2) { return 15; }
return a + b;
}
结果组装
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
.L3:
pop rbp
ret
如果您在过早返回之前查看代码(jmp .L3
,跳转到清除并返回代码),则不会调用其他指令来“准备”堆栈变量。唯一的区别是,存储在edi
和esi
寄存器中的功能参数a和b以比第一个示例([rbp-4]
和[rbp - 8]
)高的地址加载到堆栈上。这是因为没有像第一个示例中那样为本地变量“分配”额外的空间。因此,如您所见,添加这些局部变量的唯一“开销”是减法项的更改(即,甚至不添加其他减法运算)。
因此,在您的情况下,仅声明堆栈变量几乎没有任何成本。