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


    简介

    上篇中我们探索了从汇编的角度看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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    运行结果:

    100
    10 
    
    • 1
    • 2

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

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 首先是声明了变量在栈上,并赋值100
    2. 开始调用函数pb,可以看到直接将100赋值给了寄存器AX(应该编译器阅读的传参,指定给寄存器AX)
    3. 在函数pb内部(函数内部也有自己的变量,所以将传递过来的参数保存在栈上),这里赋值就直接赋值给了函数内部栈的变量了,和main函数上的不是一个,所以没有改变外部的变量
    4. 开始调用函数pbp,不像第一个第一个函数pb直接传值,而是将变量地址给寄存器AX
    5. 在函数pbp内部,虽然也将AX赋值给了函数内部的变量的,但赋值10时是直接给到了AX对应内存地址,直接改变了变量的值

    通过上面的汇编,应该有了些感悟,我们继续看看C的

    C汇编探索

    对应的C代码如下:

    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    将其生成汇编代码(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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    可以看到,C编译器约定的寄存器是eax,第一个函数复制给了函数栈上,第二个给了eax对应的内存变量

    可能对应100这个值的存储和赋值有点疑惑:

    • 100是存储在内存中,不是存放在寄存器SP中,内存中有一个程序对用栈(这个涉及到内部布局、分配相关的点,博主目前还在研究中,暂时还没有通透,等后面稍微清晰了再写篇文章讲讲)
    • SP是个寄存器,是放了100这个值对应的内存地址

    总结

    通过上面的Go和C对一个的函数调用的汇编探索,对于函数传值和传指针时能不能改变值应该有了一定的认知了

    核心感觉还是两点:

    • 函数内部变量是独立于外部的(栈是整个程序通用,只是存放在栈上的位置不同而已)
    • 指针是函数内部直接改变对应的内存地址的值

    但还是有很多的疑问:

    1. Java中是没有指针这一手的,那传值对应的情况是啥呢(Java的汇编还有点麻烦,后面再探索探索)
    2. 上面研究是基本类型,如果是字符串或者类,那是不是只有传指针,没有传值这一说
    3. 传类的时候,赋值等操作对应的汇编是怎样的,也就是汇编中是怎样对应类的操作

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

  • 相关阅读:
    使用 DDPO 在 TRL 中微调 Stable Diffusion 模型
    猿创征文|我的Go成长之路道阻且长
    亚商投资顾问 早餐FM/12022023年开展第五次全国经济普查
    MySQL查看主从复制信息详解
    Python 考试练习题 1
    C#语言async, await 简单介绍与实例(入门级)
    【SpringBoot】90、SpringBoot中@Value(“${...}“)的使用细节
    FPGA千兆网 UDP 网络视频传输,基于88E1518 PHY实现,提供工程和QT上位机源码加技术支持
    Github 2024-05-08 开源项目日报 Top10
    React 函数组件
  • 原文地址:https://blog.csdn.net/github_35735591/article/details/128191844