在之前写操作系统的过程中,我们初步接触了一些寄存器和内存的基本概念,这篇将结合这些知识重新认识下C和Go中的变量的存储
在学习java语言的时候,因为自动回收的特性,我们会关心变量的存储位置问题,是存在栈上,还是存在堆中?本文从汇编代码去看看C和Go是怎么进行变量的存储的,Java的中间还有字节码,时间问题,等后面再探索
下面先看看C和Go的示例程序和对应的汇编代码
下面是C的示例程序如下,main.c文件:
#include struct Books {int num;
};int main() {int a = getchar();struct Books book;book.num = getchar();char name[40] = "name";printf("%d, %d, %s, Hello, World!\n", a, book.num, name);return 0;
}
如上所示,我们定义了三个变量:一个int、一个字符串、一个结构体,在我们日常的程序中,变量不止这三个
编写完成后,运行下,输出正常
运行下面的命令,生成汇编代码,main.S文件:
gcc -m32 -S .\main.c
32位的寄存器目前接触的比较多,就先生成32位的,具体代码如下:
这个汇编和我们前面文章的汇编有些不同,是不同的语法,之前的是源操作在后,目标操作在前,这个是源操作在前,目标操作在后,具体可看文章:GCC汇编器语法
.file "main.c".text.def ___main; .scl 2; .type 32; .endef.section .rdata,"dr"
LC2:.ascii "%d, %d, %s, Hello, World!\12\0".text.globl _main.def _main; .scl 2; .type 32; .endef
_main:
LFB118:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5andl $-16, %espsubl $64, %espcall ___main// 调用getchar函数,默认返回结果在寄存器eax中call _getchar// 得到结果后将其放入栈中,esp是栈寄存器movl %eax, 60(%esp)call _getcharmovl %eax, 56(%esp)movdqa LC0, %xmm0movups %xmm0, 16(%esp)movdqa LC1, %xmm0movups %xmm0, 32(%esp)movl $0, 48(%esp)movl $0, 52(%esp)movl 56(%esp), %eaxleal 16(%esp), %edxmovl %edx, 12(%esp)movl %eax, 8(%esp)movl 60(%esp), %eaxmovl %eax, 4(%esp)movl $LC2, (%esp)call _printfmovl $0, %eaxleave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc
LFE118:.section .rdata,"dr".align 16
LC0:.long 1701667182.long 0.long 0.long 0.align 16
LC1:.long 0.long 0.long 0.long 0.ident "GCC: (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders) 12.2.0".def _getchar; .scl 2; .type 32; .endef.def _printf; .scl 2; .type 32; .endef
如上可以看到,对应int和结构体,得到结果后,都存到了栈上(字符串的好像也存了,但没太看懂)
接下来看看Go的
完整示例代码如下,这里就定义两个变量
package mainimport "fmt"type Books struct {name string
}func main() {var a stringfmt.Scanln(&a)book := Books{}fmt.Scanln(&book.name)b := 100fmt.Printf("hello, %s, %s, %d", a, book.name, b)
}
我们使用下面的命令生成对应的汇编代码:
go tool compile -N -l -S main.go > main.s
代码太多了,这里就不放完整的了,部分如下:汇编码很多,但会标注对应的源码中的那一行,比如下面的就是对应的源码中的10行
0x0029 00041 (main.go:10) LEAQ type.string(SB), AX0x0030 00048 (main.go:10) PCDATA $1, $00x0030 00048 (main.go:10) CALL runtime.newobject(SB)0x0035 00053 (main.go:10) MOVQ AX, main.&a+112(SP)0x003a 00058 (main.go:10) MOVQ $0, 8(AX)0x0042 00066 (main.go:10) PCDATA $0, $-20x0042 00066 (main.go:10) CMPL runtime.writeBarrier(SB), $00x0049 00073 (main.go:10) JEQ 770x004b 00075 (main.go:10) JMP 860x004d 00077 (main.go:10) MOVQ $0, (AX)0x0054 00084 (main.go:10) JMP 1030x0056 00086 (main.go:10) MOVQ AX, DI0x0059 00089 (main.go:10) XORL DX, DX0x005b 00091 (main.go:10) NOP0x0060 00096 (main.go:10) CALL runtime.gcWriteBarrierDX(SB)0x0065 00101 (main.go:10) JMP 103
关于Go的汇编语言可参考:Golang 汇编asm语言基础学习
上面放出的那部分,只是对应我们示例程序中的这一行:var a string
b := 100这个比较清晰,对应一行就是: 0x016f 00367 (main.go:14) MOVQ $100, main.b+40(SP)
在Go的汇编中,SP表示的就是栈,虽然没完成看懂这个汇编,但大致也知道了其将变量存放到栈中
试验完了,我们回过头来详细,如果我们来写一个语言,如何进行变量的存储呢?
通过前面操作系统的学习,应该是不能直接存到寄存器中,寄存器数量就那么几个
那就只能放到内存中,关于程序的内存布局有很多文章,比如C的,java和go的在我们学习中,也有堆和栈的概念
C程序的内存布局包含五个段,分别是STACK(栈段),HEAP(堆段),BSS(以符号开头的块),DS(数据段)和TEXT(文本段)。
文章参考:https://cloud.tencent.com/developer/article/1825840
综合起来,代入我们自己写一个语言,感觉应该是:
知道操作系统的相关知识感觉是真有用啊,上面是目前学到的,但由此也有了很多疑问了: