写点什么

自制操作系统番外 2:编程语言中函数参数的传递

作者:
  • 2022-12-05
    四川
  • 本文字数:1700 字

    阅读完需:约 6 分钟

简介

上篇中我们探索了从汇编的角度看 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)}
复制代码


运行结果:


10010 
复制代码


第一个并没有改变,第二个改变了,下面我们从汇编代码看看其原理

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)
复制代码


  1. 首先是声明了变量在栈上,并赋值 100

  2. 开始调用函数 pb,可以看到直接将 100 赋值给了寄存器 AX(应该编译器阅读的传参,指定给寄存器 AX)

  3. 在函数 pb 内部(函数内部也有自己的变量,所以将传递过来的参数保存在栈上),这里赋值就直接赋值给了函数内部栈的变量了,和 main 函数上的不是一个,所以没有改变外部的变量

  4. 开始调用函数 pbp,不像第一个第一个函数 pb 直接传值,而是将变量地址给寄存器 AX

  5. 在函数 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 对一个的函数调用的汇编探索,对于函数传值和传指针时能不能改变值应该有了一定的认知了


核心感觉还是两点:


  • 函数内部变量是独立于外部的(栈是整个程序通用,只是存放在栈上的位置不同而已)

  • 指针是函数内部直接改变对应的内存地址的值


但还是有很多的疑问:


  1. Java 中是没有指针这一手的,那传值对应的情况是啥呢(Java 的汇编还有点麻烦,后面再探索探索)

  2. 上面研究是基本类型,如果是字符串或者类,那是不是只有传指针,没有传值这一说

  3. 传类的时候,赋值等操作对应的汇编是怎样的,也就是汇编中是怎样对应类的操作


了解更多,不知道的更多,后面继续探索

发布于: 刚刚阅读数: 5
用户头像

关注

还未添加个人签名 2018-09-09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
自制操作系统番外2:编程语言中函数参数的传递_编程语言‘_萧_InfoQ写作社区