简介
上篇中我们探索了从汇编的角度看C和Go中的变量声明,这篇我们继续探索下函数的参数传递时的基本类型和指针类型
前言
在我们开始学习编程的时候,相信都接触过类似下面的代码吧:在函数内部改变值,一个传入的是基本类型,一个是指针类型,外部的变量是否改变
下面是go的示例代码:
package main
import "fmt"
func pb(num int) {
num = 10
}
func pbp(num *int) {
*num = 10
}
func main() {
b := 100
pb(b)
fmt.Printf("%d \n", b)
pbp(&b)
fmt.Printf("%d \n", b)
}
运行结果:
100
10
第一个并没有改变,第二个改变了,下面我们从汇编代码看看其原理
Go汇编探索
将上面的代码生成汇编代码,命令可以参考上篇
下面是关键部分代码的摘抄,就不放全部上去
// 变量b的声明:
0x0026 00038 (main.go:22) MOVQ $100, main.b+40(SP)
// 函数pb的调用
0x002f 00047 (main.go:23) MOVL $100, AX
0x0034 00052 (main.go:23) PCDATA $1, $0
0x0034 00052 (main.go:23) CALL main.pb(SB)
// 函数pb的赋值对应的代码:num = 10
0x0000 00000 (main.go:9) MOVQ AX, main.num+8(SP)
0x0005 00005 (main.go:10) MOVQ $10, main.num+8(SP)
// 函数pbp的调用
0x00c7 00199 (main.go:26) LEAQ main.b+40(SP), AX
0x00cc 00204 (main.go:26) CALL main.pbp(SB)
// 函数pbp的赋值对应的代码:*num = 10
0x0000 00000 (main.go:13) MOVQ AX, main.num+8(SP)
0x0005 00005 (main.go:14) TESTB AL, (AX)
0x0007 00007 (main.go:14) MOVQ $10, (AX)
- 首先是声明了变量在栈上,并赋值100
- 开始调用函数pb,可以看到直接将100赋值给了寄存器AX(应该编译器阅读的传参,指定给寄存器AX)
- 在函数pb内部(函数内部也有自己的变量,所以将传递过来的参数保存在栈上),这里赋值就直接赋值给了函数内部栈的变量了,和main函数上的不是一个,所以没有改变外部的变量
- 开始调用函数pbp,不像第一个第一个函数pb直接传值,而是将变量地址给寄存器AX
- 在函数pbp内部,虽然也将AX赋值给了函数内部的变量的,但赋值10时是直接给到了AX对应内存地址,直接改变了变量的值
通过上面的汇编,应该有了些感悟,我们继续看看C的
C汇编探索
对应的C代码如下:
#include <stdio.h>
void pb(int num) {
num = 10;
}
void pbp(int *num) {
*num = 10;
}
int main() {
int num = 100;
pb(num);
printf("%d\n", num);
pbp(&num);
printf("%d\n", num);
return 0;
}
将其生成汇编代码(32位的)后,对应的关键汇编代码如下:
// 变量b的声明:
movl $100, 28(%esp)
// 函数pb的调用
movl 28(%esp), %eax
movl %eax, (%esp)
call _pb
// 函数pb的赋值对应的代码:num = 10
pushl %ebp
movl %esp, %ebp
movl $10, 8(%ebp)
nop
popl %ebp
// 函数pbp的调用
leal 28(%esp), %eax
movl %eax, (%esp)
call _pbp
// 函数pbp的赋值对应的代码:*num = 10
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl $10, (%eax)
nop
popl %ebp
可以看到,C编译器约定的寄存器是eax,第一个函数复制给了函数栈上,第二个给了eax对应的内存变量
可能对应100这个值的存储和赋值有点疑惑:
- 100是存储在内存中,不是存放在寄存器SP中,内存中有一个程序对用栈(这个涉及到内部布局、分配相关的点,博主目前还在研究中,暂时还没有通透,等后面稍微清晰了再写篇文章讲讲)
- SP是个寄存器,是放了100这个值对应的内存地址
总结
通过上面的Go和C对一个的函数调用的汇编探索,对于函数传值和传指针时能不能改变值应该有了一定的认知了
核心感觉还是两点:
- 函数内部变量是独立于外部的(栈是整个程序通用,只是存放在栈上的位置不同而已)
- 指针是函数内部直接改变对应的内存地址的值
但还是有很多的疑问:
- Java中是没有指针这一手的,那传值对应的情况是啥呢(Java的汇编还有点麻烦,后面再探索探索)
- 上面研究是基本类型,如果是字符串或者类,那是不是只有传指针,没有传值这一说
- 传类的时候,赋值等操作对应的汇编是怎样的,也就是汇编中是怎样对应类的操作
了解更多,不知道的更多,后面继续探索