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