为了了解发生了什么,我使用了一个示例代码,即:
#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生成的汇编文件。下面是文件输出:
.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中。
因此,让我们制作其他版本的代码,其中新代码是:
#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;
}新生成的程序集文件是:
.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现在请注意以下几行:
# main1.c:6: int a=i;
movl 28(%esp), %eax # i, tmp84
movl %eax, 16(%esp) # tmp84, a这意味着局部变量名为,循环内部的存储在堆栈中,字节号为16,从堆栈指针库存储,而局部变量名为i存储在堆栈中,字节位置为28,与基本esp寄存器偏移。
循环结束后,创建了另外两个局部变量,它们是、a和y,它们来自以下装配代码行:
# main1.c:9: int y = 10;
movl $10, 24(%esp) #, y
# main1.c:10: int a = 5;
movl $5, 20(%esp) #, a这意味着变量a和y使用的地址为20和24,与堆栈指针相抵消,并且不重用以前的局部变量aE 234和E 135iE 236,那么为什么呢?
让我们看看另一个代码示例:
#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不销毁其作用域结束的变量并节省一些内存呢?
发布于 2022-10-07 22:09:37
请注意,如果您做同样的实验,但是使用数组(因此也存储在堆栈中,但要大得多),那么内存将被回收。
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;
}生成
(...)
movl $23, -3616(%rbp)
(...)
movl $43, -3616(%rbp)所以,不推,不弹,也不改变rbp和rsp。这不像一个新的本地范围被启动,然后弹出返回。但是arr的内存再次被用于arr2。
因此,显然,当它是重要的,无法访问的内存停留在堆栈中没有“分配”。
发布于 2022-10-07 21:45:13
若要在C中“析构”变量,则需要在离开作用域后调整堆栈指针。相反,gcc只需在函数终止时移动一次堆栈指针,您就可以获得更好的运行时。正如您已经注意到的那样,内存使用量增加了,但这通常是一个好的权衡。
发布于 2022-10-07 22:22:14
我可以回答你们的一些问题。我认为你期待的行为不是标准所要求的。
在第二个例子中:
int *p;
{
int a = 10;
p = &a;
}
printf("%d", *p);没有什么违法的事情发生。但这绝对是个常见的错误。你所做的是:
创建一个名为int
p指向的值(解释为int)
C是很原始的,你让它在那个地址打印值,它做到了。刚好是你分配给它的价值。没有人保证它将是这样的价值。无法保证您拥有p所指向的内存。没有任何保证会有什么价值。似乎你的编译器没有释放a的内存,所以你很幸运,也没有得到一个分段错误。
这就是所有的dandy..but,似乎您理解这种行为,并在问为什么。换句话说,“gcc为什么让我这么做?”答案很简单,但并不令人满意。gcc做了该做的事,不再做了。对gcc来说,立即恢复记忆实际上是没有效率的。(注:为了简单起见,我说效率低下。gcc如何判断事物是非常复杂的)。
想象一下下面的代码:
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语言编写),效率要高得多:
int main()
{
int a;
{ printf("%d", a); }
{ printf("%d", a); }
{ printf("%d", a); }
// 100 more of these lines
return 0;
}至于你问gcc为什么不马上把物品放进堆里呢,我不确定。我不熟悉添加到堆栈中的算法。我会把它留给比我更有见识的人去做。
法律免责声明:堆栈行为是gcc的工作,不是c的意见。只要效果正确,C标准就不会规定如何修改堆栈。
编辑:我很笨,显然回答了一个你从未问过的问题?我累了
https://stackoverflow.com/questions/73992807
复制相似问题