所发生的情况取决于编译器...
- 两个缓冲区都可以放在函数堆栈框架中(分别)
- 可以在由两个块(例如
either_buffer[80];
)使用的函数堆栈帧中放置一个区域
- 在每个块的开始,堆栈指针(例如
sp
)递减.对缓冲区进行操作.堆栈指针递增.
- 在每个块的开始,缓冲区被分配到堆栈指针的负偏移量(堆栈指针不变).
当然,放在function's堆栈帧中的东西只有在函数返回时才被"释放".
(1)函数栈框中的单独缓冲区(伪代码):
typedef unsigned char uint8_t;
void do_something(uint8_t *);
uint8_t *sp;
void
function(void)
{
uint8_t buffer1[80];
uint8_t buffer2[80];
{
// Do something with the buffer
do_something(buffer1);
}
{
// Do Something with the buffer
do_something(buffer2);
}
}
(2)函数栈框中的单个区域(伪代码):
typedef unsigned char uint8_t;
void do_something(uint8_t *);
uint8_t *sp;
void
function(void)
{
uint8_t either_buffer[80];
{
uint8_t *buffer1 = either_buffer;
// Do something with the buffer
do_something(buffer1);
}
{
uint8_t *buffer2 = either_buffer;
// Do Something with the buffer
do_something(buffer2);
}
}
(3)堆栈指针按需递增/递减(伪码):
typedef unsigned char uint8_t;
void do_something(uint8_t *);
uint8_t *sp;
void
function(void)
{
{
uint8_t *buffer1 = sp -= 80;
// Do something with the buffer
do_something(buffer1);
sp += 80;
}
{
uint8_t *buffer2 = sp -= 80;
// Do Something with the buffer
do_something(buffer2);
sp += 80;
}
}
(4)实际发生了什么(对于x86,使用gcc 8.3.1):
typedef unsigned char uint8_t;
void do_something(uint8_t *);
uint8_t *sp;
void
function(void)
{
{
uint8_t buffer1[80];
// Do something with the buffer
do_something(buffer1);
}
{
uint8_t buffer2[80];
// Do Something with the buffer
do_something(buffer2);
}
}
以下是汇编代码:
.file "orig.c"
.text
.comm sp,8,8
.globl function
.type function, @function
function:
.LFB0:
.cfi_startproc
pushq %rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #,
.cfi_def_cfa_register 6
subq $80, %rsp #,
# orig.c:14: do_something(buffer1);
leaq -80(%rbp), %rax #, tmp87
movq %rax, %rdi # tmp87,
call do_something #
# orig.c:20: do_something(buffer2);
leaq -80(%rbp), %rax #, tmp88
movq %rax, %rdi # tmp88,
call do_something #
# orig.c:22: }
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size function, .-function
.ident "GCC: (GNU) 8.3.1 20190223 (Red Hat 8.3.1-2)"
.section .note.GNU-stack,"",@progbits
这相当于下面的伪代码:
typedef unsigned char uint8_t;
void do_something(uint8_t *);
uint8_t *sp;
void
function(void)
{
{
uint8_t *buffer1 = sp - 80;
// Do something with the buffer
do_something(buffer1);
}
{
uint8_t *buffer2 = sp - 80;
// Do Something with the buffer
do_something(buffer2);
}
}
对于clang
7.0.1,我们将获得:
.text
.file "orig.c"
.globl function # -- Begin function function
.p2align 4, 0x90
.type function,@function
function: # @function
.cfi_startproc
# %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $160, %rsp
leaq -80(%rbp), %rdi
callq do_something
leaq -160(%rbp), %rdi
callq do_something
addq $160, %rsp
popq %rbp
.cfi_def_cfa %rsp, 8
retq
.Lfunc_end0:
.size function, .Lfunc_end0-function
.cfi_endproc
# -- End function
.type sp,@object # @sp
.comm sp,8,8
.ident "clang version 7.0.1 (Fedora 7.0.1-6.fc29)"
.section ".note.GNU-stack","",@progbits
.addrsig
.addrsig_sym function
.addrsig_sym do_something
.addrsig_sym sp
对于c++
8.3.1,它类似于gcc
:
.file "orig2.cpp"
.text
.globl sp
.bss
.align 8
.type sp, @object
.size sp, 8
sp:
.zero 8
.text
.globl _Z8functionv
.type _Z8functionv, @function
_Z8functionv:
.LFB0:
.cfi_startproc
pushq %rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #,
.cfi_def_cfa_register 6
subq $80, %rsp #,
# orig2.cpp:14: do_something(buffer1);
leaq -80(%rbp), %rax #, tmp87
movq %rax, %rdi # tmp87,
call _Z12do_somethingPh #
# orig2.cpp:20: do_something(buffer2);
leaq -80(%rbp), %rax #, tmp88
movq %rax, %rdi # tmp88,
call _Z12do_somethingPh #
# orig2.cpp:22: }
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z8functionv, .-_Z8functionv
.ident "GCC: (GNU) 8.3.1 20190223 (Red Hat 8.3.1-2)"
.section .note.GNU-stack,"",@progbits
对于gcc
,对于-m32
,我们得到:
.file "orig.c"
.text
.comm sp,4,4
.globl function
.type function, @function
function:
.LFB0:
.cfi_startproc
pushl %ebp #
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp #,
.cfi_def_cfa_register 5
subl $88, %esp #,
# orig.c:14: do_something(buffer1);
subl $12, %esp #,
leal -88(%ebp), %eax #, tmp87
pushl %eax # tmp87
call do_something #
addl $16, %esp #,
# orig.c:20: do_something(buffer2);
subl $12, %esp #,
leal -88(%ebp), %eax #, tmp88
pushl %eax # tmp88
call do_something #
addl $16, %esp #,
# orig.c:22: }
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size function, .-function
.ident "GCC: (GNU) 8.3.1 20190223 (Red Hat 8.3.1-2)"
.section .note.GNU-stack,"",@progbits
对于Cang,有-m32
个,我们得到:
.text
.file "orig.c"
.globl function # -- Begin function function
.p2align 4, 0x90
.type function,@function
function: # @function
# %bb.0:
pushl %ebp
movl %esp, %ebp
subl $168, %esp
leal -80(%ebp), %eax
movl %eax, (%esp)
calll do_something
leal -160(%ebp), %eax
movl %eax, (%esp)
calll do_something
addl $168, %esp
popl %ebp
retl
.Lfunc_end0:
.size function, .Lfunc_end0-function
# -- End function
.type sp,@object # @sp
.comm sp,4,4
.ident "clang version 7.0.1 (Fedora 7.0.1-6.fc29)"
.section ".note.GNU-stack","",@progbits
.addrsig
.addrsig_sym function
.addrsig_sym do_something
.addrsig_sym sp
上面的[实际]ASM是由without优化生成的,因此使用(例如-O2
),代码可能略有不同[not所示].
实际上,默认情况下,gcc
代码似乎比clang
代码稍微更优化.