在C/C++中,堆栈上的内存是在完成作用域块后释放的吗?它可以被重用吗?

例如,假设我在修改函数后堆栈上有100个空闲字节.

void function(void)
{
    {
        uint8_t buffer1[80];
        // Do something with the buffer
    }
    {
        uint8_t buffer2[80];
        // Do Something with the buffer
    }
}

是否可以为Buffer2分配足够的内存,或者是否只有在函数结束时才释放用于Buffer1的内存?

推荐答案

所发生的情况取决于编译器...

  1. 两个缓冲区都可以放在函数堆栈框架中(分别)
  2. 可以在由两个块(例如either_buffer[80];)使用的函数堆栈帧中放置一个区域
  3. 在每个块的开始,堆栈指针(例如sp)递减.对缓冲区进行操作.堆栈指针递增.
  4. 在每个块的开始,缓冲区被分配到堆栈指针的负偏移量(堆栈指针不变).

当然,放在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代码稍微更优化.

C++相关问答推荐

变量的const视图是否定义良好?

malloc实现:判断正确的分配对齐

sizeof结果是否依赖于字符串的声明?

为什么C语言允许你使用var =(struct NAME){

为什么内核使用扩展到前后相同的宏定义?

在一个小型玩具项目中实现终端历史记录功能

将 struct 变量赋给自身(通过指针取消引用)是否定义了行为?

将常量转换为指针会增加.数据大小增加1000字节

SSH会话出现意外状态

如何使用_newindex数组我总是得到错误的参数

初始成员、公共初始序列、匿名联合和严格别名如何在C中交互?

为什么这个分配做得不好呢?

为什么会导致分段故障?(C语言中的一个程序,统计文件中某个单词的出现次数)

在Ubuntu上使用库部署C程序的最佳实践

将char*数组深度复制到 struct 中?

在同一范围内对具有相同类型的变量执行的相同操作在同一C代码中花费的时间不同

使用替代日历打印日期

strlen 可以是[[未排序]]吗?

inline 关键字导致 Clion 中的链接器错误

计算 e^x 很好.但不是 x 的罪