golang 返回值和c的底层区别
1、栈帧
栈帧结构的两端由两个指针来指定。寄存器ebp通常用做帧指针(frame pointer),而esp则用作栈指针(stack pointer)。在函数执行过程中,栈指针esp会随着数据的入栈和出栈而移动,因此函数中对大部分数据的访问都基于帧指针ebp进行。
esp和ebp: esp是栈指针,是cpu机制决定的,push、pop指令会自动调整esp的值;ebp只是存取某时刻的esp,这个时刻就是进入一个函数内后,cpu会将esp的值赋给ebp,此时就可以通过ebp对栈进行操作,比如获取函数参数,局部变量等,实际上使用esp也可以;
-
EBP:基址指针寄存器(extended base pointer),存放一个指针,永远指向系统栈最上面一个栈帧的底部
-
ESP:栈指针寄存器(extended stack pointer),存放一个指针,永远指向系统栈最上面一个栈帧的栈顶
-
EAX 寄存器也叫做累加寄存器,用于存储函数的返回值外也用于执行计算的 操作。
-
调用函数中push ebp,将main函数的ebp压栈,然后mov ebp, esp将当前函数的esp赋给ebp,得到当前函数的栈底地址。
//函数调用之所以能够返回,单靠保持返回地址是不够的,这一步压栈动作很重要,
//因为我们要标记函数调用者栈帧的帧底,这样才能找出保存了的返回地址,
//栈顶是不用保存的,因为上一个栈帧的顶部会是func的栈帧底部。(两栈帧相邻的)
push ebp;
//mov指令将esp寄存器的值赋值给ebp寄存器。上一栈帧的顶部,就是这个栈帧的底部
mov ebp, esp;
- 调用函数结束之前,执行leave指令,其实该指令等于下面两条指令:此时fun相关数据全部被出栈,ebp将得重新到main函数的栈底地址,esp回到函数栈顶部.
mov esp, ebp //mov 将ebp的值 赋值给esp
pop ebp //将堆栈中的ebp值弹出
2、go汇编中有4个伪寄存器
- FP: Frame pointer,指向栈底位置,一般用来引用函数的输入参数,用来访问函数的参数
- PC: Program counter: 程序计数器,用于分支和跳转
- SB: Static base pointer: 一般用于声明函数或者全局变量
- SP: Stack pointer:指向当前栈帧的局部变量的开始位置(栈顶位置),一般用来引用函数的局部变量
在 C 语言中调用一个函数,函数的参数是通过寄存器和栈传递的,在 x86_64 的机器上,6 个以下(含 6 个)的参数会按照顺序分别使用 edi、esi、edx、ecx、r8d 和 r9d 六个寄存器传递,超过 6 个的剩余参数会通过栈进行传递;函数的返回值是通过 eax 寄存器进行传递的,这也就是为什么 C 语言中不支持多个返回值。
fp+offset
+-----------+---\
| 返回值2 | \
+-----------+ \
| 返回值1 | \
+---------+-+
| 参数2 | 这些在调用函数中
+-----------+
| 参数1 | /
+-----------+ /
| 返回地址 | /
+-----------+--\/-----fp值
| 局部变量 | \
| ... | 被调用数栈祯
| | /
+-----------+--/+---sp值
golang channel的底层实现
1、chan的实现在runtime/chan.go中,channel底层是一个hchan的结构体,根据有无缓冲区分为:有缓冲channel和无缓冲channel.
make(chan int) // 无缓存 chan
make(chan int, 10) // 有缓存 chan
//底层数据结构
type hchan struct {
buf unsafe.Pointer // 存放实际数据的指针,用unsafe.Pointer存放地址,为了避免gc
qcount uint // 队列中的数据个数
dataqsiz uint // 环形队列的大小,channel本身是一个环形队列
elemsize uint16
closed uint32 // 标识channel是否关闭
elemtype *_type // 数据 元素类型
sendx uint // send的 index
recvx uint // recv 的 index
recvq waitq // 阻塞在 recv 的队列
sendq waitq // 阻塞在 send 的队列
lock mutex // 锁
}
bufsendxrecvxbuflockrecvqsendq

2、send(channel <- xxx)/recv(<-channel)的操作的细节可以细化为:
- 第一,加锁
- 第二,把数据从goroutine中copy到“队列”中(或者从队列中copy到goroutine中)。
- 第三,释放锁
3、当 channel 缓存满了,或者没有缓存的时候,我们继续 send(ch <- xxx) 或者 recv(<- ch) 会阻塞当前 goroutine。
sudogsendq