当前位置: 首页 > news >正文

Go 语言函数调用参数传递规则

1. 调试环境

Go 版本:1.19.3

Gdb 版本:12.1

CPU 架构:amd64

Linux 发行版:Ubuntu 22.04

Linux 内核版本:5.15.0-48

2. 函数调用参数传递规则版本变化

在 Go 中函数间进行调用时,主调(caller)在调用被调(callee)函数之前需要先将参数存储到某个地方,以供被调者使用。

在 Go 1.7 之前,函数入参都是存储在堆栈上。主调会先在堆栈上划出一块内存,然后将调用参数值存储到这块堆栈上,最后调用 call 指令跳转到被调函数执行,被调函数执行时,会从主调分配的那块堆栈上获取入参值。

在 Go 1.7 及以后版本对入参传递方式进行了优化,函数入参主要通过寄存器传递,寄存器不够用了才会将入参存储到栈上。这个优化将 Go 程序执行效率提高了 5%,生成的二进制文件大小减少了 2%。详见 Go 1.17 Release Notes。

在 amd64 的架构的 CPU 上,Go 使用了 RAX、RBX、RCX、RDI, RSI、R8、R9、R10 和 R11 共计 9 个整数寄存器来存储整型入参,同时通过 X0~X14 共计15个浮点数寄存器来存储浮点数入参。这些寄存器的顺序也依次对应函数入参从左到右的顺序。

如果入参是结构体,递归拆解其成员的类型最终也只会是整型或者浮点型,仍可以用寄存器传递其值。

3. 验证参数传递规则正确性

下面在 Go1.19.3 版本上,分别对入参类型为整数、浮点数、数组和结构体时参数的传递方式进行验证。

3.1. 注意事项

3.1.1. 关闭编译器内联

验证的被调函数非常简单,如果正常编译会被编译器内联到主调函数里面,就不会有参数的传递。为了达到验证效果,要关闭编译器内联。有两种方式来关闭编译器内联:

  • 被调函数前加上 //go:noinline 编译标签。

  • Go 编译命令中加上 -gcflags "-l" 标志。

3.1.2. 打开编译器优化

在编译时最好别使用禁止优化的选项,即不要在 build 时加 -gcflags "-N" 标志,如果禁止优化,入参的传递规则在某些场景下会有点混乱(没有理清楚),以下示例都是在打开编译器优化的情况下执行的。

3.2. 编译汇编代码

可用用 Go 自带的 objdump 工具会将二进制反汇编成 plan9 汇编,也可以使用 Linux 系统自带的 objdump 工具会将二进制反汇编成 at&t 汇编,两种不同的汇编语言在语法有一点差异,但并不会导致入参规则上的变化,因此任选一种自已熟悉的汇编语言学习即可,本文档使用 plan9 汇编。

命令语法:

go build -o main main.go
$GOROOT/pkg/tool/linux_amd64/objdump main > main.plan9.asm

3.3. 验证整数做为入参

整数寄存器有 9 个,分别对整数入参个数为小于等于 9 个 和大于 9 个两种情况进行验证。

3.3.1. 整数入参为 9 个

示例中定义了一个 add 函数,有 9 个入参,1 个返回值。如下:

//go:noinline
func add(a, b, c, d, e, f, g, h, i int) int {return a+b+c+d+e+f+g+h+i
}func main() {add(1, 2, 3, 4, 5, 6, 7, 8, 9)
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/int_less_ten.go...   int_less_ten.go:9 0x457c54        b801000000      MOVL $0x1, AX      int_less_ten.go:9 0x457c59        bb02000000      MOVL $0x2, BX      int_less_ten.go:9 0x457c5e        b903000000      MOVL $0x3, CX      int_less_ten.go:9 0x457c63        bf04000000      MOVL $0x4, DI      int_less_ten.go:9 0x457c68        be05000000      MOVL $0x5, SI      int_less_ten.go:9 0x457c6d        41b806000000    MOVL $0x6, R8      int_less_ten.go:9 0x457c73        41b907000000    MOVL $0x7, R9      int_less_ten.go:9 0x457c79        41ba08000000    MOVL $0x8, R10      int_less_ten.go:9 0x457c7f        41bb09000000    MOVL $0x9, R11      int_less_ten.go:9 0x457c85        e896ffffff      CALL main.add(SB) 

从 main.main 汇编指令 3~11 行可以看到, 分别将 0-9 这 9 个数字存储到 RAX、RBX、RCX、RDI, RSI、R8、R9、R10 和 R11 这 9 个寄存器中。在准备好入参后,会通过 call 指令跳转到 main.add 函数执行,main.add 汇编代码如下:

TEXT main.add(SB) /home/ubuntu/program/reg_demo/int_less_ten.goint_less_ten.go:5 0x457c20        488d1403        LEAQ 0(BX)(AX*1), DX    int_less_ten.go:5 0x457c24        4801d1          ADDQ DX, CX     int_less_ten.go:5 0x457c27        4801f9          ADDQ DI, CX     int_less_ten.go:5 0x457c2a        4801f1          ADDQ SI, CX     int_less_ten.go:5 0x457c2d        4c01c1          ADDQ R8, CX     int_less_ten.go:5 0x457c30        4c01c9          ADDQ R9, CX     int_less_ten.go:5 0x457c33        4c01d1          ADDQ R10, CX      int_less_ten.go:5 0x457c36        498d040b        LEAQ 0(R11)(CX*1), AX    int_less_ten.go:5 0x457c3a        c3              RET   

在 main.add 函数中,会直接从寄存器中取入参的值,并进行计算。

汇编代码第二行的指令 LEAQ 0(BX)(AX*1), DX 看着有复杂,但其作用就是将 RAX 与 RBX 寄存器的值相加并赋值给 RDX,可等价为如下指令:

MOVQ $0, DX; 
ADDQ AX, DX; 
ADDQ BX, DX

通过 LEAQ 的方式可以一条指令就代替下面 3 条指令,执行效率更高。这是编译器优化的结果,如果在编译程序时带上了禁止优化 -gcflags "-N" 标志,就会从栈上依次取出参数并相加。

验证结果表明,当入参为整数时,且入参个数小于等于 9 个时,全部通过寄存器传递。

3.3.2. 整数入参为 10 个

示例中定义了一个 add 函数,有 10 个入参,1 个返回值。如下:

//go:noinline
func add(a, b, c, d, e, f, g, h, i, j int) int {return a+b+c+d+e+f+g+h+i+j
}func main() {add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/int_more_ten.go...   int_more_ten.go:9 0x457c74        48c704240a000000    MOVQ $0xa, 0(SP)      int_more_ten.go:9 0x457c7c        b801000000      	MOVL $0x1, AX      int_more_ten.go:9 0x457c81        bb02000000      	MOVL $0x2, BX      int_more_ten.go:9 0x457c86        b903000000     	 	MOVL $0x3, CX      int_more_ten.go:9 0x457c8b        bf04000000      	MOVL $0x4, DI      int_more_ten.go:9 0x457c90        be05000000      	MOVL $0x5, SI      int_more_ten.go:9 0x457c95        41b806000000        MOVL $0x6, R8      int_more_ten.go:9 0x457c9b        41b907000000        MOVL $0x7, R9      int_more_ten.go:9 0x457ca1        41ba08000000        MOVL $0x8, R10      int_more_ten.go:9 0x457ca7        41bb09000000        MOVL $0x9, R11      int_more_ten.go:9 0x457cad        e86effffff      	CALL main.add(SB)

从上面编指令第 3 行可以看到,主调函数 main.man 会先将入参 10 放到堆栈 SP 处,然后 4~12 行依次将剩余的 0-9 九个入参放到寄存器中。在准备好入参后,会通过 call 指令跳转到 main.add 函数执行,main.add 汇编代码如下:

TEXT main.add(SB) /home/ubuntu/program/reg_demo/int_more_ten.goint_more_ten.go:5 0x457c20        488d1403        LEAQ 0(BX)(AX*1), DX    int_more_ten.go:5 0x457c24        4801d1          ADDQ DX, CX     int_more_ten.go:5 0x457c27        4801f9          ADDQ DI, CX     int_more_ten.go:5 0x457c2a        4801f1          ADDQ SI, CX     int_more_ten.go:5 0x457c2d        4c01c1          ADDQ R8, CX     int_more_ten.go:5 0x457c30        4c01c9          ADDQ R9, CX     int_more_ten.go:5 0x457c33        4c01d1          ADDQ R10, CX      int_more_ten.go:5 0x457c36        4c01d9          ADDQ R11, CX      int_more_ten.go:5 0x457c39        488b542408      MOVQ 0x8(SP), DX    int_more_ten.go:5 0x457c3e        488d040a        LEAQ 0(DX)(CX*1), AX    int_more_ten.go:5 0x457c42        c3          	RET  

在 main.add 的汇编代码第 2~9 行可以看到,会直接从寄存器中取前 9 个入参的值,并进行加运算,将运算结果放到 RCX 寄存器中。然后在第 10 行,会先将第 10 个参数从堆栈中取中,并放到寄存器 RDX 中,最后将 RDX 与 RCX 相加,并将结果赋给 RAX,并用 RET 指令返回。

验证结果表明,当入参个数大于 9 个时,前 0~9 个是通过寄存器传递,剩下的是通过堆栈传递。

3.4. 浮点数

首先验证入参中有浮点数时是否是通过浮点数寄存器传递,再验证入参中同时包含浮点数和整数时,是否同时通过浮点数寄存器和整数寄存器进行传递。

注:不做寄存器上限验证,上面的 [验证整数做为入参](#3. 验证参数传递规则正确性) 已经做过了。

3.4.1. 入参为浮点数

示例中定义了一个 add 函数,有 2 个浮点数入参,1 个返回值。如下:

//go:noinline
func add(x, y float64) float64 {return x+y 
}func main() {_ = add(1.1, 2.2)
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/float.go...   float.go:9        0x457c54        f20f10053c3b0200    MOVSD_XMM $f64.3ff199999999999a(SB), X0 float.go:9        0x457c5c        f20f100d443b0200    MOVSD_XMM $f64.400199999999999a(SB), X1 float.go:9        0x457c64        e8b7ffffff      	CALL main.add(SB)  

从上面汇编指令 3~4 行可以看到, 主调函数 main.main 分别将两个浮点数入参存储到 X0 和 X1 两个寄存器中。在准备好入参后,会通过 call 指令跳转到 main.add 函数执行,main.add 汇编代码如下:

TEXT main.add(SB) /home/ubuntu/program/reg_demo/float.gofloat.go:5        0x457c20        f20f58c1        ADDSD X1, X0      float.go:5        0x457c24        c3          	RET  

在被调 main.add 的汇编代码中可以看到,直接对寄存器 X0 和 X1 进行计算。

验证结果表明,当入参为浮点数时,入参是通过 X0~X14 寄存器传递的。

3.4.2. 入参为浮点数和整数

示例中定义了一个 add 函数,有两个参数,一个为整数,一个为浮点数,1 个返回值。如下:

//go:noinline
func add(x int, y float64) int {return x+ int(y)
}func main() {_ = add(1, 2.2)
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/mixed.go...    mixed.go:10       0x457c54        b801000000      	MOVL $0x1, AX      mixed.go:10       0x457c59        f20f1005473b0200    MOVSD_XMM $f64.400199999999999a(SB), X0 mixed.go:10       0x457c61        e8baffffff      	CALL main.add(SB) 

从上面汇编指令 3~4 行可以看到,两个参数被分别存储到 RAX 整数寄存器和 X0 浮点数寄存器。准备好入参后,会通过 call 指令跳转到 main.add 函数执行,main.add 汇编代码如下:

TEXT main.add(SB) /home/ubuntu/program/reg_demo/mixed.gomixed.go:6        0x457c20        f2480f2cc8      CVTTSD2SIQ X0, CX    mixed.go:6        0x457c25        4801c8          ADDQ CX, AX     mixed.go:6        0x457c28        c3          	RET 

在被调 main.add 的汇编代码中可以看到,直接对寄存器 RAX 和 X1 进行计算,并将结果存入 RAX,并返回。

验证结果表明,当入参同时有整数和浮点数时,整数入参通过 RAX、RBX、RCX、RDI, RSI、R8、R9、R10 和 R11 寄存器传递,浮点数入参通过 X0~X14 寄存器传递。

3.5. 数组

验证当数组大小未超过9个时,入参是否通过寄存器传递。

3.5.1. 入参为大小小于9的数组

示例中定义了一个 add 函数,有一个类型为数组的参数,1 个返回值。如下:

//go:noinline
func add(x [5]int) int {return x[0] + x[3]
}func main() {_ = add([5]int{1, 2, 3, 4, 5})
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/array.go... array.go:10       0x457c54        48c7042401000000    MOVQ $0x1, 0(SP)      array.go:10       0x457c5c        48c744240802000000  MOVQ $0x2, 0x8(SP)      array.go:10       0x457c65        48c744241003000000  MOVQ $0x3, 0x10(SP)      array.go:10       0x457c6e        48c744241804000000  MOVQ $0x4, 0x18(SP)      array.go:10       0x457c77        48c744242005000000  MOVQ $0x5, 0x20(SP)      array.go:10       0x457c80        e89bffffff      	CALL main.add(SB)  

从上面汇编指令 3~7 行可以看到,入参被放在堆栈中。准备好入参后,会通过 call 指令跳转到 main.add 函数执行,main.add 汇编代码如下:

TEXT main.add(SB) /home/ubuntu/program/reg_demo/array.goarray.go:6        0x457c20        488b442408      MOVQ 0x8(SP), AX    array.go:6        0x457c25        4803442420      ADDQ 0x20(SP), AX    array.go:6        0x457c2a        c3          	RET  

在被调 main.add 的汇编代码中可以看到,从堆栈中取值放到寄存器,然后进行计算,并返回。

验证结果表明,当入参的类型为数组时,无论数组大小是否超过寄存器数量,总是通过堆栈来传递。

3.6. 结构体

验证重点:

  • 结构体中只含有整数成员,且成员个数小于等于 9 个时,结构体如何传递;

  • 结构体中只含有整数成员,且成员个数大于 9 个时,结构体如何传递;

  • 结构体中包含数组成员,结构体如何传递;

  • 结构体中同时包含整数和浮点数成员,且整数成员个数小于等于 9 个,浮点数成员小于等于 15 个,结构体如何传递。

  • 结构体中同时包含整数和浮点数成员,且整数成员个数小于等于 9 个,浮点数成员大于 15 个,结构体如何传递。

3.6.1. 结构体基本整数成员小于等于9个

示例中定义了一个 addStruct 函数,有一个类型为 Elem 结构体的参数,其下有 3 个 byte 类型的成员,6 个 int 类型的成员。如下:

type Elem struct {a, b, c byted, e, f, g, h, i int
}//go:noinline
func addStruct(e Elem) int {return int(e.a)+e.i
}func main() {_ = addStruct(Elem{1, 2, 3, 4, 5, 6, 7, 8, 9})
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/struct.go...    struct.go:13      0x457c74        b801000000      MOVL $0x1, AX      struct.go:13      0x457c79        bb02000000      MOVL $0x2, BX      struct.go:13      0x457c7e        b903000000      MOVL $0x3, CX      struct.go:13      0x457c83        bf04000000      MOVL $0x4, DI      struct.go:13      0x457c88        be05000000      MOVL $0x5, SI      struct.go:13      0x457c8d        41b806000000    MOVL $0x6, R8      struct.go:13      0x457c93        41b907000000    MOVL $0x7, R9      struct.go:13      0x457c99        41ba08000000    MOVL $0x8, R10      struct.go:13      0x457c9f        41bb09000000    MOVL $0x9, R11      struct.go:13      0x457ca5        e876ffffff      CALL main.addStruct(SB)

从上面汇编指令 3~11 行可以看到,结构体的 9 个成员值分别被放到 9 个整数寄存器中。准备好入参后,会通过 call 指令跳转到 main.addStruct 函数执行,main.addStruct汇编代码如下:

  struct_align.go:9	 	0x457c20        88442408        MOVB AL, 0x8(SP)    struct_align.go:9 	0x457c24        885c2409        MOVB BL, 0x9(SP)    struct_align.go:9 	0x457c28        884c240a        MOVB CL, 0xa(SP)    struct_align.go:9 	0x457c2c        48897c2410      MOVQ DI, 0x10(SP)   struct_align.go:9 	0x457c31        4889742418      MOVQ SI, 0x18(SP)   struct_align.go:9 	0x457c36        4c89442420      MOVQ R8, 0x20(SP)   struct_align.go:9 	0x457c3b        4c894c2428      MOVQ R9, 0x28(SP)   struct_align.go:9 	0x457c40        4c89542430      MOVQ R10, 0x30(SP)  struct_align.go:9 	0x457c45        4c895c2438      MOVQ R11, 0x38(SP)  struct_align.go:10    0x457c4a        0fb64c2408      MOVZX 0x8(SP), CX    struct_align.go:10    0x457c4f        4a8d0419        LEAQ 0(CX)(R11*1), AX    struct_align.go:10    0x457c53        c3          	RET 

从上面汇编代码可以看到,从寄存器中取出入参值,并放到堆栈上,需要特别注意的是,这里放在堆栈上并不是按地址总线宽度,而是以内存对齐的规则来放置的,如 a,b,c 这三个变量是 byte 类型,在内存对齐的规则中是会放在一个 8 字节的内存中,上面 1~3 行汇编代码也印证了这点。

验证结果表明,当结构体做为入参,且结构体成员个数小于 10 个时,会通过寄存器传递结构体成员值。同时在被调函数中,会将寄存器中的值以内存对齐的方式转存在堆栈上,方便后续计算。

同理,当结构体成员中,浮点数个数不超过15个时,会通过寄存器传递。

3.6.2. 结构体基本整数成员大于 9 个

示例中定义了一个 addStruct 函数,有一个类型为 Elem 结构体的参数,其下有 3 个 byte 类型的成员,7 个 int 类型的成员。如下:

type Elem struct {a, b, c byted, e, f, g, h, i, j int
}//go:noinline
func addStruct(e Elem) int {return int(e.a)+e.i
}func main() {_ = addStruct(Elem{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/struct_more_ten.go..    struct_more_ten.go:14 0x457c54        48c7042401020300    MOVQ $0x30201, 0(SP)      struct_more_ten.go:14 0x457c5c        48c744240804000000  MOVQ $0x4, 0x8(SP)      struct_more_ten.go:14 0x457c65        48c744241005000000  MOVQ $0x5, 0x10(SP)      struct_more_ten.go:14 0x457c6e        48c744241806000000  MOVQ $0x6, 0x18(SP)      struct_more_ten.go:14 0x457c77        48c744242007000000  MOVQ $0x7, 0x20(SP)      struct_more_ten.go:14 0x457c80        48c744242808000000  MOVQ $0x8, 0x28(SP)      struct_more_ten.go:14 0x457c89        48c744243009000000  MOVQ $0x9, 0x30(SP)      struct_more_ten.go:14 0x457c92        48c74424380a000000  MOVQ $0xa, 0x38(SP)      struct_more_ten.go:14 0x457c9b        0f1f440000      	NOPL 0(AX)(AX*1)      struct_more_ten.go:14 0x457ca0        e87bffffff      	CALL main.addStruct(SB)    

从上面汇编指令 3~10 行可以看到,结构体的 10 个成员值分别被堆栈中。准备好入参后,会通过 call 指令跳转到 main.addStruct 函数执行,main.addStruct汇编代码如下:

TEXT main.addStruct(SB) /home/ubuntu/program/reg_demo/struct_more_ten.gostruct_more_ten.go:10 0x457c20        0fb6442408      MOVZX 0x8(SP), AX    struct_more_ten.go:10 0x457c25        4803442438      ADDQ 0x38(SP), AX    struct_more_ten.go:10 0x457c2a        c3          	RET  

从上面汇编代码可以看到,直接从堆栈中取出入参值进行计算。

验证结果表明,当结构体做为入参,且结构体成员个数大于 9 个时,整个结构体的所有成员都通过堆栈传递。

同理,当结构体成员中,浮点数个数超过15个时,整个结构体的所有成员也都通过堆栈传递。

3.6.3. 结构体成员含有数组时

示例中定义了一个 addStruct 函数,其入参类型为 Elem 结构体,该结构体成员含有数组。如下:

type Elem struct {a [4]int
}//go:noinline
func addStruct(e Elem) int {return e.a[0] + e.a[3]
}func main() {_ = addStruct(Elem{a: [4]int{1,2,3,4}})
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/struct_array.go..struct_array.go:13    0x457c54        48c7042401000000    MOVQ $0x1, 0(SP)      struct_array.go:13    0x457c5c        48c744240802000000  MOVQ $0x2, 0x8(SP)      struct_array.go:13    0x457c65        48c744241003000000  MOVQ $0x3, 0x10(SP)      struct_array.go:13    0x457c6e        48c744241804000000  MOVQ $0x4, 0x18(SP)      struct_array.go:13    0x457c77        e8a4ffffff      	CALL main.addStruct(SB)      TEXT main.addStruct(SB) /home/ubuntu/program/reg_demo/struct_array.gostruct_array.go:9 0x457c20        488b442408      MOVQ 0x8(SP), AX    struct_array.go:9 0x457c25        4803442420      ADDQ 0x20(SP), AX    struct_array.go:9 0x457c2a        c3          	RET      

从上面汇编代码中可以看到,主调 main.main 在调用 main.addStruct 函数前,先将参数存放在堆栈,被调会从堆栈中取出参数值进行计算。

验证结果表明,当结构体成员有数组时,只能通过堆栈传递。

3.6.4. 结构体整数成员 9 个,浮点数成员 15 个

示例中定义了一个 addStruct 函数,其入参类型为 Elem 结构体,该结构体有 9 个整数类型成员,15 个浮点数类型成员。如下:

type Elem struct {a, b, c, d, e, f, g, h, i intj, k, l, m, n, o, p, q, r, s, t, u, v, w, x float64
}//go:noinline
func addStruct(e Elem) int {return e.a+int(e.m)
}func main() {_ = addStruct(Elem{1, 2, 3, 4, 5, 6, 7, 8, 9, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.1, 11.11, 12.12, 13.13, 14.14, 15.15})
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.main(SB) /home/ubuntu/program/reg_demo/struct_mixed.go.. struct_mixed.go:14    0x457d06        f20f100512400200    MOVSD_XMM 0x24012(IP), X0       struct_mixed.go:14    0x457d0e        f20f100d12400200    MOVSD_XMM 0x24012(IP), X1       ..struct_mixed.go:14    0x457d7c        f2440f10350b400200  MOVSD_XMM 0x2400b(IP), X14     struct_mixed.go:14    0x457d85        b801000000      	MOVL $0x1, AX               struct_mixed.go:14    0x457d8a        bb02000000      	MOVL $0x2, BX               ..        struct_mixed.go:14    0x457db0        41bb09000000        MOVL $0x9, R11              struct_mixed.go:14    0x457db6        e865feffff      	CALL main.addStruct(SB)      TEXT main.addStruct(SB) /home/ubuntu/program/reg_demo/struct_mixed.gostruct_mixed.go:9 0x457c20        4889442408      	    MOVQ AX, 0x8(SP)    struct_mixed.go:9 0x457c25        48895c2410      		MOVQ BX, 0x10(SP)   ...struct_mixed.go:9 0x457c48        4c895c2448      		MOVQ R11, 0x48(SP) struct_mixed.go:9 0x457c4d        f20f11442450        	MOVSD_XMM X0, 0x50(SP)  struct_mixed.go:9 0x457c53        f20f114c2458        	MOVSD_XMM X1, 0x58(SP)  struct_mixed.go:9 0x457c59        f20f11542460        	MOVSD_XMM X2, 0x60(SP)  ...struct_mixed.go:9 0x457cbf        f2440f11b424c0000000    MOVSD_XMM X14, 0xc0(SP) struct_mixed.go:10    0x457cc9    f20f10442468        	MOVSD_XMM 0x68(SP), X0  struct_mixed.go:10    0x457ccf    f2480f2cc0      		CVTTSD2SIQ X0, AX    struct_mixed.go:10    0x457cd4    4803442408      		ADDQ 0x8(SP), AX    struct_mixed.go:10    0x457cd9    c3          			RET     

从上面汇编代码中可以看到(代码有省略),主调 main.main 先将结构体成员值放入寄存器, 被调函数执行时,再从寄存器上将值取出并计算。

验证结果表明,结构体中同时包含整数和浮点数成员,且整数成员个数小于等于 9 个,浮点数成员小于等于 15 个,结构体通过寄存器传递。

3.6.5. 结构体整数成员 9 个,浮点数成员 16 个

示例中定义了一个 addStruct 函数,其入参类型为 Elem 结构体,该结构体有 9 个整数类型成员,16 个浮点数类型成员。如下:

type Elem struct {a, b, c, d, e, f, g, h, i intj, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y float64
}//go:noinline
func addStruct(e Elem) int {return e.a+int(e.m)
}func main() {_ = addStruct(Elem{1, 2, 3, 4, 5, 6, 7, 8, 9, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.1, 11.11, 12.12, 13.13, 14.14, 15.15, 16.16})
}

编译程序,并将编译成的二进制反汇编成汇编语言。

TEXT main.addStruct(SB) /home/ubuntu/program/reg_demo/struct_mixed_more.gostruct_mixed_more.go:10   0x457c20        f20f10442468    MOVSD_XMM 0x68(SP), X0  struct_mixed_more.go:10   0x457c26        f2480f2cc0      CVTTSD2SIQ X0, AX    struct_mixed_more.go:10   0x457c2b        4803442408      ADDQ 0x8(SP), AX    struct_mixed_more.go:10   0x457c30        c3          	RET      TEXT main.main(SB) /home/ubuntu/program/reg_demo/struct_mixed_more.go...    struct_mixed_more.go:14   0x457c62        48c7042401000000    MOVQ $0x1, 0(SP)      struct_mixed_more.go:14   0x457c6a        488d7c2408      	LEAQ 0x8(SP), DI      struct_mixed_more.go:14   0x457c6f        488d3532410200      LEAQ 0x24132(IP), SI      struct_mixed_more.go:14   0x457c76        660f1f840000000000  NOPW 0(AX)(AX*1)      struct_mixed_more.go:14   0x457c7f        90          		NOPL      struct_mixed_more.go:14   0x457c80        48896c24f0      	MOVQ BP, -0x10(SP)      struct_mixed_more.go:14   0x457c85        488d6c24f0      	LEAQ -0x10(SP), BP      struct_mixed_more.go:14   0x457c8a        e8c9d9ffff      	CALL 0x455658      struct_mixed_more.go:14   0x457c8f        488b6d00        	MOVQ 0(BP), BP      struct_mixed_more.go:14   0x457c93        e888ffffff      	CALL main.addStruct(SB)  

从上面汇编代码中可以看到,主调 main.main 在调用 main.addStruct 函数前,先将结构体值从只读数据段(0x24132(IP))处拷贝到堆栈上,被调会从堆栈中取出参数值进行计算。

验证结果表明,结构体中同时包含整数和浮点数成员,且整数成员个数小于等于 9 个,浮点数成员大于 15 个,结构体通过堆栈传递。

进一步可得出结论,当结构体成员中整数成员超过整数寄存器个数或浮点数成员超过浮点数寄存器个数时,都会通过堆栈传递。

总结

  • RAX、RBX、RCX、RDI, RSI、R8、R9、R10 和 R11 为整数寄存器,这 9 个寄存器的顺序也依次对应函数入参从左到右的顺序。

  • X0~X14 为浮点数寄存器,X0、X1、X2 ... 的顺序也依次对应函数入参从左到右的顺序。

  • 入参为整数时,会优先通过整数寄存器传递,当整数寄存器不够时,通过堆栈传递。

  • 入参为浮点数时,会优先通过浮点数寄存器传递,当浮点数寄存器不够时,通过堆栈传递。

  • 入参为数组时,只能通过堆栈传递。

  • 当结构体做为函数入参时,结构体所包含的基本成员,要么全部通过寄存器传递,要么全部通过栈传递;不能一部分通过寄存器传递,一部分通过栈传递。

  • 入参为结构体时,当结构体成员有数组时,整个结构体只能通过堆栈传递。

  • 入参为结构体时,当整数成员小于9个,且浮点数成员小于15个时,整个结构体通过寄存器传递。

  • 入参为结构体时,当整数成员大于9个,或浮点数成员大于15个时,整个结构体通过堆栈传递。

相关文章:

Go 语言函数调用参数传递规则

1. 调试环境Go 版本:1.19.3Gdb 版本:12.1CPU 架构:amd64Linux 发行版:Ubuntu 22.04Linux 内核版本:5.15.0-482. 函数调用参数传递规则版本变化在 Go 中函数间进行调用时,主调(caller&#xff09…...

二分查找【零神基础精讲】

来源0x3f:https://space.bilibili.com/206214 文章目录二分查找[34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)[162. 寻找峰值](https://leetcode.cn/problems/find-p…...

「计算机组成原理」数据的表示和运算(上)

文章目录一、进位计数制1.1 其他进制转十进制1.2 十进制转其他进制1.3 二进制、八进制和十六进制1.3 真值和机器数二、BCD码2.1 8421码2.2 余3码2.3 2421码三、整数的表示和运算3.1 无符号整数3.1.1 无符号整数的表示3.1.2 无符号整数的运算3.2 有符号整数3.2.1 有符号整数的表…...

分层,均质,稀薄燃烧

均质燃烧: 只能使用火花点燃。 即为普通燃烧方式,燃料和空气混合形成一定浓度的可燃混合气(厂家自配),整个燃烧室内混合气的空燃比是相同的,经火花塞点燃燃烧。这种燃烧方式使燃料和空气充分混合,燃料完全燃烧,从而获得大的输出功率。为使混合…...

mybatis-plus小课堂:多表查询【案例篇】(apply 拼接 in SQL,来查询从表某个范围内的数据)

文章目录 引言I 多表查询1.1 多表查询:在mapper.xml 写语句和拼接查询条件1.2 多表关联:Java代码中书写语句和拼接查询条件1.3 案例:左外连接II mybatis-Plus 之 apply 拼接 in SQL2.1 apply源码实现2.2 apply 拼接 in SQLIII 常见问题3.1 Cause: comColumn xxx in where cl…...

HashMap原理详解

一、hashmap简介 hashmap是Java当中一种数据结构,是一个用于存储Key-Value键值对的集合,每一个键值对也叫作Entry。 二、JDK7的HashMap1、JDK7时HashMap的数据结构 1、在JDK7之前,hashmap底层采用数组链表的数据结构来存储数据 2、插入数据采…...

推荐3款远程办公软件

一款好用的远程办公软件能够大大的提高我们的办公效率,在这篇文章中,我们将为您推荐几款常见又好用的远程办公软件,以帮助您能更加高效的远程办公。电脑远程办公软件有很多,本文主要从团队沟通软件、视频会议软件、远程控制软件等…...

计算机中有符号数的表示

文章目录二进制数制十进制二进制位模式基本数据类型无符号数的编码有符号数的编码原码(Sign-Magnitude)反码(Ones Complement)补码(Twos Complement)概念导读编码格式按权展开补码加法扩展一个数字的位表示…...

MySQL(一)服务器连接 库的基本操作

目录 一、连接服务器 二、简单使用 三、校验规则 条件筛选 where 进行order排序 三、查看数据库 使用 show databases;(注意分号和最后一个s) 显示创建数据库的详情信息:使用show create database test2; 四、修改数据库 五…...

Maven怎样构建生命周期?

项目构建生命周期Maven的本质是一个项目管理工具,将项目开发和管理过程抽象成一个项目对象模型(POM)。Maven构建生命周期描述的是一次构建过程经历经历了多少个事件。对项目构建的生命周期划分为3套,其中clean负责清理工作,default负责核心工…...

真实3D地形生成器【免费在线】

Terrain3D是一个免费的在线3D地形生成器,只需指定地球上的坐标,就可以自动生成附近区域的3D地形同时叠加卫星影像,并且可以导出GLTF格式的3D地形模型。 推荐:使用 NSDT场景设计器 快速搭建 3D场景。 使用Terrain3D生成真实世界的3…...

华为OD机试 - 整数编码(Python)

整数编码 题目 实现一个整数编码方法 使得待编码的数字越小 编码后所占用的字节数越小 编码规则如下 编码时7位一组,每个字节的低 7 位用于存储待编码数字的补码字节的最高位表示后续是否还有字节,置1表示后面还有更多的字节,置0表示当前字节为最后一个字节采用小端序编码…...

【GlobalMapper精品教程】051:融合Dissolve操作详解

本节讲解globalmapper中融合Dissolve工具的使用。 文章目录 一、工具介绍1. 工具位置2. 融合工具二、案例实战1. 加载实验数据2. 根据字段分组融合案例一:根据地类名称分组,将相同的类型融合到一起。案例二:根据权属地类名称分组,将相同的类型融合到一起。一、工具介绍 1.…...

Java Excel的数据导入导出

引入依赖 <!-- EasyExcel --> <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.7</version> </dependency><!--csv文件操作--> <dependency><groupId>n…...

OceanBase 4.0解读:兼顾高效与透明,我们对DDL的设计与思考

关于作者 谢振江&#xff0c;OceanBase 高级技术专家。 2015年加入 OceanBase, 从事存储引擎相关工作&#xff0c;目前在存储-索引与 DDL 组&#xff0c;负责索引&#xff0c;DDL 和 IO 资源调度相关工作。 回顾关系型数据库大规模应用以来的发展&#xff0c;从单机到分布式无…...

Qt线程池

目录1、线程池是什么&#xff1f;2、Qt线程池2.1、用法例程2.2、线程池对性能的提升2.3、运行算法单线程写法线程池写法1、线程池是什么&#xff1f; 线程池是一种线程使用模式&#xff0c;它管理着一组可重用的线程&#xff0c;可以处理分配过来的可并发执行的任务。 线程池设…...

设置table中的tbody

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>设置table中的tbody</title> </head> <body> <script> // 这里有json数据&#xff0c;是jav…...

2023美赛A题完整数据!思路代码数据数学建模

选取内蒙古河套灌区&#xff08;典型干旱区&#xff09;2010-2020年气温&#xff0c;降雨&#xff0c;蒸散发和水汽压月数据 包括四种主要作物及其占比 内容截图如下&#xff1a; 链接为&#xff1a;https://www.jdmm.cc/file/2708703 同时还提供参考代码和参考文章的选项~…...

Node.js安装与配置

Node.js安装与配置 前言 本篇博文记录了Node.js安装与环境变量配置的详细步骤&#xff0c;旨在为将来再次配置Node.js时提供指导方法。 另外&#xff1a;Node.js版本请根据自身系统选择&#xff0c;安装位置、全局模块存放位置和环境变量应根据自身实际情况进行更改。 Node…...

k8s(存储)数据卷与数据持久卷

为什么需要数据卷&#xff1f; 容器中的文件在磁盘上是临时存放的&#xff0c;这给容器中运行比较重要的应用程序带来一些问题问题1&#xff1a;当容器升级或者崩溃时&#xff0c;kubelet会重建容器&#xff0c;容器内文件会丢失问题2&#xff1a;一个Pod中运行多个容器并需要共…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…...

Docker拉取MySQL后数据库连接失败的解决方案

在使用Docker部署MySQL时&#xff0c;拉取并启动容器后&#xff0c;有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致&#xff0c;包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因&#xff0c;并提供解决方案。 一、确认MySQL容器的运行状态 …...

GAN模式奔溃的探讨论文综述(一)

简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...