当前位置: 首页 > 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中运行多个容器并需要共…...

php5.6.9安装sqlsrv扩展(windows)

报错:Marning: PHP Startup: Unable to load dynamic 1library D:lphpstudy_prolExtensionslphpl(phps.6.9ntslextphp_ pdo_sqlsry 56 nts′找不到指定的模块。in Unknown on line 0 整整搞了一天才终于解决 我用的是phpstudy_pro&#xff08;也就是小皮v8.1版本&#xff09;&…...

URL黑名单 扫描工具ua特征 GET(args)参数检查 cookie黑名单 POST参数检查参考代码

资源宝分享www.httple.net 文章目录URL黑名单扫描工具ua特征GET(args)参数检查cookie黑名单POST参数检查注&#xff1a;请先检查是否已设置URL白名单&#xff0c;若已设置URL白名单&#xff0c;URL黑名单设置将失效 多个URL配置需换行&#xff0c;一行只允许填写一个。可直接填…...

【软考系统架构设计师】2022下论文写作历年真题

【软考系统架构设计师】2022下论文写作历年真题 试题四 论湖仓一体架构及其应用&#xff08;75分&#xff09; 试题四 论湖仓一体架构及其应用 随着5G、大数据、人工智能、物联网等技术的不断成熟&#xff0c;各行各业的业务场景日益复杂&#xff0c;企业数据呈现出大规模、多…...

推荐3个好用的scrum敏捷项目管理工具

结合对工具的了解和使用心得&#xff0c;介绍在管理scrum中常见的一些工具基础的scrum工具&#xff1a;1、物理白板物理白板是实施scrum最简单直接的方式。之前我也说过&#xff0c;一些利弊。数据不能够沉淀等等。2、Excel表格表格的形式就是如果多人编辑时&#xff0c;不能实…...

每日学术速递2.17

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.LG 1.Decoupled Model Schedule for Deep Learning Training 标题&#xff1a;深度学习训练的解耦模型时间表 作者&#xff1a;Hongzheng Chen, Cody Hao Yu, Shuai Zheng, Zhen Zhang,…...

ElementUI`resetFields()`方法避坑

使用ElementUI中的resetFields()方法有哪些注意点 场景一 场景一&#xff1a;当编辑弹出框和新增弹出框共用时&#xff0c;编辑数据后关闭编辑弹出框时调用this.$refs.form.resetFields()无法清空弹出框 问题代码&#xff1a; // 点击新增按钮handleAdd() {this.dialogVi…...

如何保证数据库和缓存双写一致性?

前言 数据库和缓存&#xff08;比如&#xff1a;redis&#xff09;双写数据一致性问题&#xff0c;是一个跟开发语言无关的公共问题。尤其在高并发的场景下&#xff0c;这个问题变得更加严重。 我很负责的告诉大家&#xff0c;该问题无论在面试&#xff0c;还是工作中遇到的概率…...

Hinge Loss 和 Zero-One Loss

文章目录Hinge Loss 和 Zero-One LossHinge LossZero-One LossHinge Loss 和 Zero-One Loss 维基百科&#xff1a;https://en.wikipedia.org/wiki/Hinge_loss 图表说明&#xff1a; 纵轴表示固定 t1t1t1 的 Hinge loss&#xff08;蓝色&#xff09;和 Zero-One Loss&#xff…...

Linux下zabbix_proxy实施部署

简介 zabbix proxy 可以代替 zabbix server 收集性能和可用性数据,然后把数据汇报给 zabbix server,并且在一定程度上分担了zabbix server 的压力. zabbix-agent可以指向多个proxy或者server zabbix-proxy不能指向多个server zabbix proxy 使用场景: 1&#xff0c;监控远程区…...

Rust之错误处理(二):带结果信息的可恢复错误

开发环境 Windows 10Rust 1.67.1VS Code 1.75.1项目工程 这里继续沿用上次工程rust-demo 带结果信息的可恢复错误 大多数错误并没有严重到需要程序完全停止的程度。有时&#xff0c;当一个函数失败时&#xff0c;它的原因是你可以很容易地解释和应对的。例如&#xff0c;如…...

哪个网站生鲜配送做的好/世界杯数据分析

1、描述 python中isinstance()函数&#xff0c;是python中的一个内置函数&#xff0c;用来判断一个函数是否是一个已知的类型&#xff0c;类似type()。 2、语法 isinstance&#xff08;object,classinfo&#xff09; 参数&#xff1a; object:实例对象 classinfo&#xff1a;可…...

大兴高端网站建设/下载百度2024最新版

效果图 image.png 提供的方法 和 属性 ref.show() // ref 主动调用显示打开ref.hide() // ref 主动调用隐藏关闭modalBoxHeight: 300, // 盒子高度modalBoxBg: #fff, // 盒子背景色hide: function () { }, // 关闭时的回调函数transparentIsClick: true // 透明区域是否可以点…...

网站建设推广者怎样找到客户/广州关键词快速排名

Bluemix是IBM推出的开放的PaaS云平台&#xff0c;包含大量平台和软件服务&#xff0c;旨在帮助开发者实现一站式地应用开发与部署管理。 2016年&#xff0c;Bluemix面向开发者推出了基于超级账本Fabric的区块链服务&#xff0c;供全球的区块链爱好者使用。用户可以通过访问http…...

成都o2o网站建设/谷歌自然排名优化

往期精选● 架构师高并发高性能分布式教程(4000G)● 39阶段精品云计算大数据实战视频教程● 互联网技术干货视频教程大全【菜单为准】● 2017年8月最新Intellij IDEA全套视频教程● 程序员如何制作高质量的简历【视频简历】● 两套大型电商实战项目 ● 200本经典编程相关…...

wordpress 时间轴主题/网页设计制作

session并不是浏览器关闭时销毁的&#xff0c;而是在session失效的时候销毁下列代码就是监测session创建、销毁package com.my.count;import javax.servlet.http.*;public class SessionCounter implements HttpSessionListener {private static int activeSessions 0;//sessi…...

随州网站设计开发服务/电商怎么做营销推广

*.settings bin gen .classpath .project *.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo __pycache__ *.rej *~ #*# .#* .*.swp .DS_Store...