go语言的函数栈帧是 golang函数调用栈

"栈"和"栈帧"这两个概念到底如何区分

1、栈:FILO先进后出的数据结构

创新互联公司专业网站建设、成都网站制作,集网站策划、网站设计、网站制作于一体,网站seo、网站优化、网站营销、软文发稿等专业人才根据搜索规律编程设计,让网站在运行后,在搜索中有好的表现,专业设计制作为您带来效益的网站!让网站建设为您创造效益。

栈底是第一个进栈的数据的位置(压箱 底)

栈顶是最后一个进栈的数据位置

2、根据SP指针指向的位置,栈可分为 满栈和空栈

满栈:当sp指针总是指向最后压入堆栈 的数据(ARM采用满栈)

空栈:当堆栈指针SP总是指向下一个将 要放入数据的空位置。

3、根据SP指针移动的方向,可分为升 栈和降栈

升栈:随数据的入栈,SP由低地址-- 高地址

降栈:随数据的入栈,SP由高地址-- 低地址(ARM采用降栈)

4、栈帧:存储在用户栈上的(当然内核栈同样适用)每一次函数调用涉及的相关信息的记录单元 ; 栈帧(stack frame)就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。

栈帧的两个边界分别有FP(R11)和SP(R13)L来限定。

栈帧

栈的作用:

1)保存局部变量

分析代码:

[html] view plain copy

#include stdio.h

int main()

{

int a;

a++;

return a;

}/span

反汇编之后的代码;

[html] view plain copy

stack: file format elf32-littlearm

Disassembly of section .text:

00000000 main:

#include stdio.h

int main()

{

0:   e52db004 push   {fp}     ; (str fp, [sp, #-4]!) @将栈帧底部指针FP压入栈中;创建属于main函数的栈帧。

4:   e28db000 add    fp, sp, #0  ; 0x0 @fp指针为函数栈帧的底部,

8:   e24dd00c sub    sp, sp, #12 ; 0xc   @sp指针为栈帧的顶部,同时为栈的栈顶。

int a;

a++;

c:   e51b3008 ldr    r3, [fp, #-8]   @由此三句可知变量a在栈帧中执行了加法操作,及栈帧具有保存局部变量的作用

10:   e2833001 add    r3, r3, #1  ; 0x1

14:   e50b3008 str    r3, [fp, #-8]

return a;

18:   e51b3008 ldr    r3, [fp, #-8]

}

/span

2)保存函数的参数

分析代码:

[html] view plain copy

span style="font-size:18px;"#include stdio.h

void func1(int a,int b,int c,int d,int e,int f)

{

int k;

k=e+f;

}

int main()

{

func1(1,2,3,4,5,6);

return 0;

}

反汇编之后的代码;

void func1(int a,int b,int c,int d,int e,int f) @多于4个参数

{

0:   e52db004 push   {fp}     ; (str fp, [sp, #-4]!)@保存main函数的栈帧底部指针FP

4:   e28db000 add    fp, sp, #0  ; 0x0

8:   e24dd01c sub    sp, sp, #28 ; 0x1c @由栈帧顶部指针SP创建一片栈帧保存子函数的前四个参数

c:   e50b0010 str    r0, [fp, #-16]  @ a

10:   e50b1014 str    r1, [fp, #-20]  @ b

14:   e50b2018 str    r2, [fp, #-24]  @ c

18:   e50b301c str    r3, [fp, #-28]  @ d

int k;

k=e+f;

1c:   e59b3004 ldr    r3, [fp, #4]    @在子函数的栈帧中实现第五个参数与第六个参数的运算

20:   e59b2008 ldr    r2, [fp, #8] @由ldr  r2, [fp, #8]知参数保存在main函数的栈帧中,并运算

24:   e0833002 add    r3, r3, r2   @以子函数的栈帧底部指针(fp)做参考坐标实现对参数的查找

28:   e50b3008 str    r3, [fp, #-8]

}

2c:   e28bd000 add    sp, fp, #0  ; 0x0

30:   e8bd0800 pop    {fp}

34:   e12fff1e bx lr

00000038 main:

int main()

{

38:   e92d4800 push   {fp, lr}    @由于调用子函数,先保存main函数的栈帧底部指针FP和返回地址LR(当前PC指针的下一地址)

3c:   e28db004 add    fp, sp, #4  ; 0x4 @可知先压入FP,后压入lr.把此时子函数(被调用者)的栈帧底部指针FP指向保存在子函数栈帧的main函数(调用者)的栈帧底部指针FP

40:   e24dd008 sub    sp, sp, #8  ; 0x8   @创建栈

func1(1,2,3,4,5,6);

44:   e3a03005 mov    r3, #5  ; 0x5

48:   e58d3000 str    r3, [sp]

4c:   e3a03006 mov    r3, #6  ; 0x6

50:   e58d3004 str    r3, [sp, #4]

54:   e3a00001 mov    r0, #1  ; 0x1 @用通用寄存器保存前四个参数的值

58:   e3a01002 mov    r1, #2  ; 0x2

5c:   e3a02003 mov    r2, #3  ; 0x3

60:   e3a03004 mov    r3, #4  ; 0x4

64:   ebfffffe bl 0 func1

return 0;

68:   e3a03000 mov    r3, #0  ; 0x0

}

6c:   e1a00003 mov    r0, r3

70:   e24bd004 sub    sp, fp, #4  ; 0x4

74:   e8bd4800 pop    {fp, lr}

78:   e12fff1e bx lr/span

注:C中,若函数的参数小于等于4个,则用通用寄存器保存其参数值,多于4个的参数保存在栈中

3)保存寄存器的值

分析代码:

[html] view plain copy

span style="font-size:18px;"include stdio.h

void func2(int a,int b)

{

int k;

k=a+b;

}

void func1(int a,int b)

{

int c;

func2(3,4);

c=a+b;

}

int main()

{

func1(1,2);

return 0;

}/span

反汇编之后的代码;

[html] view plain copy

span style="font-size:18px;"void func2(int a,int b)

{

0:   e52db004 push   {fp}     ; (str fp, [sp, #-4]!)

4:   e28db000 add    fp, sp, #0  ; 0x0

8:   e24dd014 sub    sp, sp, #20 ; 0x14

c:   e50b0010 str    r0, [fp, #-16] @保存寄存器的值

10:   e50b1014 str    r1, [fp, #-20]

int k;

k=a+b;

14:   e51b3010 ldr    r3, [fp, #-16]

18:   e51b2014 ldr    r2, [fp, #-20]

1c:   e0833002 add    r3, r3, r2

20:   e50b3008 str    r3, [fp, #-8]

}

24:   e28bd000 add    sp, fp, #0  ; 0x0

28:   e8bd0800 pop    {fp}

2c:   e12fff1e bx lr

00000030 func1:

void func1(int a,int b)

{

30:   e92d4800 push   {fp, lr}

34:   e28db004 add    fp, sp, #4  ; 0x4

38:   e24dd010 sub    sp, sp, #16 ; 0x10

3c:   e50b0010 str    r0, [fp, #-16] @代码44行调用func2函数后,又使用r0\r1保存参数,所以此时将r0\r1寄存器的

40:   e50b1014 str    r1, [fp, #-20]  @值放入栈中

int c;

func2(3,4);

44:   e3a00003 mov    r0, #3  ; 0x3

48:   e3a01004 mov    r1, #4  ; 0x4

4c:   ebfffffe bl 0 func2

c=a+b;

50:   e51b3010 ldr    r3, [fp, #-16]

54:   e51b2014 ldr    r2, [fp, #-20]

58:   e0833002 add    r3, r3, r2

5c:   e50b3008 str    r3, [fp, #-8]

}

60:   e24bd004 sub    sp, fp, #4  ; 0x4

64:   e8bd4800 pop    {fp, lr}

68:   e12fff1e bx lr

0000006c main:

int main()

{

6c:   e92d4800 push   {fp, lr}

70:   e28db004 add    fp, sp, #4  ; 0x4

func1(1,2);

74:   e3a00001 mov    r0, #1  ; 0x1

78:   e3a01002 mov    r1, #2  ; 0x2

7c:   ebfffffe bl 30 func1

return 0;

80:   e3a03000 mov    r3, #0  ; 0x0

}

84:   e1a00003 mov    r0, r3

88:   e24bd004 sub    sp, fp, #4  ; 0x4

8c:   e8bd4800 pop    {fp, lr}

90:   e12fff1e bx lr/span

初始化栈:即对SP指针赋予一个内存地址(统一标准:2440、6410、210)

在内存的64MB位置即ldr sp, =0x34000000(2440)

ldr sp, =0x54000000(6410)

ldr sp, =0x24000000(210)

由上可知ARM采用满栈(指向刚入栈的数据)、降栈(由高地址向低地址入栈)

问题:因为ARM不同工作模式有不同的栈,定义栈的技巧是什么,避免定义相同的地址使用不同栈?

转自:

栈帧是什么

栈帧也叫过程活动记录,是编译器用来实现过程或函数调用的一种数据结构。

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。

栈帧,顾名思义,就是栈中的一帧,栈分成很多帧,就如同一个视频动作分成好多帧一样。每个栈帧,对应一个函数,就是这个函数在栈中占用的部分。

golang - channel

通过var声明或者make函数创建的channel变量是一个存储在函数栈帧上的指针,占用8个字节,指向堆上的hchan结构体

源码包中src/runtime/chan.go定义了hchan的数据结构如下:

hchan结构体的主要组成部分有四个:

用来保存goroutine之间传递数据的循环数组:buf

用来记录此循环数组当前发送或接收数据的下标值:sendx和recvx

用于保存向该chan发送和从该chan接收数据被阻塞的goroutine队列: sendq 和 recvq

保证channel写入和读取数据时线程安全的锁:lock

环形数组作为channel 的缓冲区 数组的长度就是定义channnel 时channel 的缓冲大小

在hchan 中包括了读/写 等待队列, waitq是一个双向队列,包括了一个头结点和尾节点。 每个节点是一个sudog结构体变量

channel有2种类型:无缓冲、有缓冲, 在创建时 make(chan type cap) 通过cap 设定缓冲大小

channel有3种模式:写操作模式(单向通道)、读操作模式(单向通道)、读写操作模式(双向通道)

channel有3种状态:未初始化、正常、关闭

如下几种状态会引发panic

channel 是线程安全的,channel的底层实现中,hchan结构体中采用Mutex锁来保证数据读写安全。在对循环数组buf中的数据进行入队和出队操作时,必须先获取互斥锁,才能操作channel数据


本文名称:go语言的函数栈帧是 golang函数调用栈
标题路径:http://ybzwz.com/article/doeoesg.html