首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >局部变量在其作用域结束后不会被销毁。

局部变量在其作用域结束后不会被销毁。
EN

Stack Overflow用户
提问于 2022-10-07 21:38:29
回答 3查看 77关注 0票数 0

为了了解发生了什么,我使用了一个示例代码,即:

代码语言:javascript
复制
#include <stdio.h>
int main()
{
  for(int i=0;i<5;i++)
  {
      int a=i;
      printf("a=%x\n",&a);
  }

    return 0;
}

我用这个命令行gcc -fverbose-asm main1.c -S -o main1.s生成gcc生成的汇编文件。下面是文件输出:

代码语言:javascript
复制
        .file   "main1.c"
 # GNU C17 (MinGW.org GCC Build-2) version 9.2.0 (mingw32)
 #  compiled by GNU C version 9.2.0, GMP version 6.1.2, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.21-GMP

 # GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
 # options passed:  -iprefix c:\mingw\bin\../lib/gcc/mingw32/9.2.0/ main1.c
 # -mtune=generic -march=i586 -auxbase-strip main1.s -fverbose-asm
 # options enabled:  -faggressive-loop-optimizations -fassume-phsa
 # -fasynchronous-unwind-tables -fauto-inc-dec -fcommon
 # -fdelete-null-pointer-checks -fdwarf2-cfi-asm -fearly-inlining
 # -feliminate-unused-debug-types -ffp-int-builtin-inexact -ffunction-cse
 # -fgcse-lm -fgnu-runtime -fgnu-unique -fident -finline-atomics
 # -fipa-stack-alignment -fira-hoist-pressure -fira-share-save-slots
 # -fira-share-spill-slots -fivopts -fkeep-inline-dllexport
 # -fkeep-static-consts -fleading-underscore -flifetime-dse
 # -flto-odr-type-merging -fmath-errno -fmerge-debug-strings -fpeephole
 # -fplt -fprefetch-loop-arrays -freg-struct-return
 # -fsched-critical-path-heuristic -fsched-dep-count-heuristic
 # -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic
 # -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic
 # -fsched-stalled-insns-dep -fschedule-fusion -fsemantic-interposition
 # -fset-stack-executable -fshow-column -fshrink-wrap-separate
 # -fsigned-zeros -fsplit-ivs-in-unroller -fssa-backprop -fstdarg-opt
 # -fstrict-volatile-bitfields -fsync-libcalls -ftrapping-math
 # -ftree-cselim -ftree-forwprop -ftree-loop-if-convert -ftree-loop-im
 # -ftree-loop-ivcanon -ftree-loop-optimize -ftree-parallelize-loops=
 # -ftree-phiprop -ftree-reassoc -ftree-scev-cprop -funit-at-a-time
 # -funwind-tables -fverbose-asm -fzero-initialized-in-bss -m32 -m80387
 # -m96bit-long-double -maccumulate-outgoing-args -malign-double
 # -malign-stringops -mavx256-split-unaligned-load
 # -mavx256-split-unaligned-store -mfancy-math-387 -mfp-ret-in-387
 # -mieee-fp -mlong-double-80 -mms-bitfields -mno-red-zone -mno-sse4
 # -mpush-args -msahf -mstack-arg-probe -mstv -mvzeroupper

    .text
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "a=%x\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB13:
    .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    #,
 # main1.c:3: {
    call    ___main  #
 # main1.c:4:   for(int i=0;i<5;i++)
    movl    $0, 28(%esp)     #, i
 # main1.c:4:   for(int i=0;i<5;i++)
    jmp L2   #
L3:
 # main1.c:6:       int a=i;
    movl    28(%esp), %eax   # i, tmp84
    movl    %eax, 24(%esp)   # tmp84, a
 # main1.c:7:       printf("a=%x\n",&a);
    leal    24(%esp), %eax   #, tmp85
    movl    %eax, 4(%esp)    # tmp85,
    movl    $LC0, (%esp)     #,
    call    _printf  #
 # main1.c:4:   for(int i=0;i<5;i++)
    addl    $1, 28(%esp)     #, i
L2:
 # main1.c:4:   for(int i=0;i<5;i++)
    cmpl    $4, 28(%esp)     #, i
    jle L3   #,
 # main1.c:10:  return 0;
    movl    $0, %eax     #, _5
 # main1.c:11: }
    leave   
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret 
    .cfi_endproc
LFE13:
    .ident  "GCC: (MinGW.org GCC Build-2) 9.2.0"
    .def    _printf;    .scl    2;  .type   32; .endef

请注意,通过程序集文件中的下列代码行:

main1.c:6: int a=i;movl 28(%esp),%eax # i,tmp84 movl %eax,24(%esp) # tmp84,a

这意味着名为的局部变量a存储在堆栈中,字节数为24,而局部变量i存储在堆栈中的字节位置号28中。

因此,让我们制作其他版本的代码,其中新代码是:

代码语言:javascript
复制
    #include <stdio.h>
int main()
{
    for(int i=0;i<5;i++)
    {
        int a=i;
        printf("a=%x\n",&a);
    }
    int y = 10;
    int a = 5;

    return 0;
}

新生成的程序集文件是:

代码语言:javascript
复制
        .file   "main1.c"
 # GNU C17 (MinGW.org GCC Build-2) version 9.2.0 (mingw32)
 #  compiled by GNU C version 9.2.0, GMP version 6.1.2, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.21-GMP

 # GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
 # options passed:  -iprefix c:\mingw\bin\../lib/gcc/mingw32/9.2.0/ main1.c
 # -mtune=generic -march=i586 -auxbase-strip main2.s -fverbose-asm
 # options enabled:  -faggressive-loop-optimizations -fassume-phsa
 # -fasynchronous-unwind-tables -fauto-inc-dec -fcommon
 # -fdelete-null-pointer-checks -fdwarf2-cfi-asm -fearly-inlining
 # -feliminate-unused-debug-types -ffp-int-builtin-inexact -ffunction-cse
 # -fgcse-lm -fgnu-runtime -fgnu-unique -fident -finline-atomics
 # -fipa-stack-alignment -fira-hoist-pressure -fira-share-save-slots
 # -fira-share-spill-slots -fivopts -fkeep-inline-dllexport
 # -fkeep-static-consts -fleading-underscore -flifetime-dse
 # -flto-odr-type-merging -fmath-errno -fmerge-debug-strings -fpeephole
 # -fplt -fprefetch-loop-arrays -freg-struct-return
 # -fsched-critical-path-heuristic -fsched-dep-count-heuristic
 # -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic
 # -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic
 # -fsched-stalled-insns-dep -fschedule-fusion -fsemantic-interposition
 # -fset-stack-executable -fshow-column -fshrink-wrap-separate
 # -fsigned-zeros -fsplit-ivs-in-unroller -fssa-backprop -fstdarg-opt
 # -fstrict-volatile-bitfields -fsync-libcalls -ftrapping-math
 # -ftree-cselim -ftree-forwprop -ftree-loop-if-convert -ftree-loop-im
 # -ftree-loop-ivcanon -ftree-loop-optimize -ftree-parallelize-loops=
 # -ftree-phiprop -ftree-reassoc -ftree-scev-cprop -funit-at-a-time
 # -funwind-tables -fverbose-asm -fzero-initialized-in-bss -m32 -m80387
 # -m96bit-long-double -maccumulate-outgoing-args -malign-double
 # -malign-stringops -mavx256-split-unaligned-load
 # -mavx256-split-unaligned-store -mfancy-math-387 -mfp-ret-in-387
 # -mieee-fp -mlong-double-80 -mms-bitfields -mno-red-zone -mno-sse4
 # -mpush-args -msahf -mstack-arg-probe -mstv -mvzeroupper

    .text
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "a=%x\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB13:
    .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    #,
 # main1.c:3: {
    call    ___main  #
 # main1.c:4:     for(int i=0;i<5;i++)
    movl    $0, 28(%esp)     #, i
 # main1.c:4:     for(int i=0;i<5;i++)
    jmp L2   #
L3:
 # main1.c:6:         int a=i;
    movl    28(%esp), %eax   # i, tmp84
    movl    %eax, 16(%esp)   # tmp84, a
 # main1.c:7:         printf("a=%x\n",&a);
    leal    16(%esp), %eax   #, tmp85
    movl    %eax, 4(%esp)    # tmp85,
    movl    $LC0, (%esp)     #,
    call    _printf  #
 # main1.c:4:     for(int i=0;i<5;i++)
    addl    $1, 28(%esp)     #, i
L2:
 # main1.c:4:     for(int i=0;i<5;i++)
    cmpl    $4, 28(%esp)     #, i
    jle L3   #,
 # main1.c:9:     int y = 10;
    movl    $10, 24(%esp)    #, y
 # main1.c:10:  int a = 5;
    movl    $5, 20(%esp)     #, a
 # main1.c:12:     return 0;
    movl    $0, %eax     #, _7
 # main1.c:13: }
    leave   
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret 
    .cfi_endproc
LFE13:
    .ident  "GCC: (MinGW.org GCC Build-2) 9.2.0"
    .def    _printf;    .scl    2;  .type   32; .endef

现在请注意以下几行:

代码语言:javascript
复制
 # main1.c:6:         int a=i;
movl    28(%esp), %eax   # i, tmp84
movl    %eax, 16(%esp)   # tmp84, a

这意味着局部变量名为,循环内部的存储在堆栈中,字节号为16,从堆栈指针库存储,而局部变量名为i存储在堆栈中,字节位置为28,与基本esp寄存器偏移。

循环结束后,创建了另外两个局部变量,它们是、ay,它们来自以下装配代码行:

代码语言:javascript
复制
# main1.c:9:     int y = 10;
movl    $10, 24(%esp)    #, y
# main1.c:10:  int a = 5;
movl    $5, 20(%esp)     #, a

这意味着变量ay使用的地址为2024,与堆栈指针相抵消,并且不重用以前的局部变量aE 234E 135iE 236,那么为什么呢?

让我们看看另一个代码示例:

代码语言:javascript
复制
    #include <stdio.h>
int main()
{
    int *ptr;
    for(int i=0;i<5;i++)
    {
        int a=10;
        ptr = &a;
        int x;
    }
    int y = 10;

    printf("a = %d\n",*ptr); // how come a = 10?
    return 0;
}

在这段代码中,我创建了一个悬空指针并注意到输出:

那么为什么gcc不销毁其作用域结束的变量并节省一些内存呢?

EN

回答 3

Stack Overflow用户

发布于 2022-10-07 22:09:37

请注意,如果您做同样的实验,但是使用数组(因此也存储在堆栈中,但要大得多),那么内存将被回收。

代码语言:javascript
复制
int testfunc(){
   for(int i=0; i<10; i++){
      int arr[1000];
      arr[100]=23; // I use specific numbers to find it faster in generated code
   }
   int arr2[1000];
   arr[100]=43;
}

生成

代码语言:javascript
复制
    (...)
    movl    $23, -3616(%rbp)
    (...)
    movl    $43, -3616(%rbp)

所以,不推,不弹,也不改变rbp和rsp。这不像一个新的本地范围被启动,然后弹出返回。但是arr的内存再次被用于arr2

因此,显然,当它是重要的,无法访问的内存停留在堆栈中没有“分配”。

票数 1
EN

Stack Overflow用户

发布于 2022-10-07 21:45:13

若要在C中“析构”变量,则需要在离开作用域后调整堆栈指针。相反,gcc只需在函数终止时移动一次堆栈指针,您就可以获得更好的运行时。正如您已经注意到的那样,内存使用量增加了,但这通常是一个好的权衡。

票数 0
EN

Stack Overflow用户

发布于 2022-10-07 22:22:14

我可以回答你们的一些问题。我认为你期待的行为不是标准所要求的。

在第二个例子中:

代码语言:javascript
复制
int *p;
{
  int a = 10;
  p = &a;
}
printf("%d", *p);

没有什么违法的事情发生。但这绝对是个常见的错误。你所做的是:

创建一个名为int

  • stuff

  • printing的指针,指向
  1. 在地址p指向

的值(解释为int)

C是很原始的,你让它在那个地址打印值,它做到了。刚好是你分配给它的价值。没有人保证它将是这样的价值。无法保证您拥有p所指向的内存。没有任何保证会有什么价值。似乎你的编译器没有释放a的内存,所以你很幸运,也没有得到一个分段错误。

这就是所有的dandy..but,似乎您理解这种行为,并在问为什么。换句话说,“gcc为什么让我这么做?”答案很简单,但并不令人满意。gcc做了该做的事,不再做了。对gcc来说,立即恢复记忆实际上是没有效率的。(注:为了简单起见,我说效率低下。gcc如何判断事物是非常复杂的)。

想象一下下面的代码:

代码语言:javascript
复制
int main()
{
  { int a; printf("%d", a); }
  { int a; printf("%d", a); }
  { int a; printf("%d", a); }
  // 100 more of these lines
  return 0;
}

gcc在每一个街区之后都要保持记忆是合法的。gcc在每个街区之后清理内存也是合法的。如果gcc按照以下方式重新使用代码(为便于与最后一个代码块进行比较,用c语言编写),效率要高得多:

代码语言:javascript
复制
int main()
{
  int a;
  { printf("%d", a); }
  { printf("%d", a); }
  { printf("%d", a); }
  // 100 more of these lines
  return 0;
}

至于你问gcc为什么不马上把物品放进堆里呢,我不确定。我不熟悉添加到堆栈中的算法。我会把它留给比我更有见识的人去做。

法律免责声明:堆栈行为是gcc的工作,不是c的意见。只要效果正确,C标准就不会规定如何修改堆栈。

编辑:我很笨,显然回答了一个你从未问过的问题?我累了

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73992807

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档