写点什么

自制操作系统番外:编程语言中变量是如何存储的

作者:
  • 2022-12-04
    四川
  • 本文字数:2405 字

    阅读完需:约 8 分钟

简介

在之前写操作系统的过程中,我们初步接触了一些寄存器和内存的基本概念,这篇将结合这些知识重新认识下 C 和 Go 中的变量的存储

前言

在学习 java 语言的时候,因为自动回收的特性,我们会关心变量的存储位置问题,是存在栈上,还是存在堆中?本文从汇编代码去看看 C 和 Go 是怎么进行变量的存储的,Java 的中间还有字节码,时间问题,等后面再探索


下面先看看 C 和 Go 的示例程序和对应的汇编代码

C 语言的示例程序和汇编代码

下面是 C 的示例程序如下,main.c 文件:


#include <stdio.h>
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_startproc  pushl  %ebp  .cfi_def_cfa_offset 8  .cfi_offset 5, -8  movl  %esp, %ebp  .cfi_def_cfa_register 5  andl  $-16, %esp  subl  $64, %esp  call  ___main  // 调用getchar函数,默认返回结果在寄存器eax中  call  _getchar  // 得到结果后将其放入栈中,esp是栈寄存器  movl  %eax, 60(%esp)  call  _getchar  movl  %eax, 56(%esp)  movdqa  LC0, %xmm0  movups  %xmm0, 16(%esp)  movdqa  LC1, %xmm0  movups  %xmm0, 32(%esp)  movl  $0, 48(%esp)  movl  $0, 52(%esp)  movl  56(%esp), %eax  leal  16(%esp), %edx  movl  %edx, 12(%esp)  movl  %eax, 8(%esp)  movl  60(%esp), %eax  movl  %eax, 4(%esp)  movl  $LC2, (%esp)  call  _printf  movl  $0, %eax  leave  .cfi_restore 5  .cfi_def_cfa 4, 4  ret  .cfi_endprocLFE118:  .section .rdata,"dr"  .align 16LC0:  .long  1701667182  .long  0  .long  0  .long  0  .align 16LC1:  .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 的

Go 示例程序和汇编

完整示例代码如下,这里就定义两个变量


package main
import "fmt"
type Books struct { name string}
func main() { var a string fmt.Scanln(&a) book := Books{} fmt.Scanln(&book.name) b := 100 fmt.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), AX  0x0030 00048 (main.go:10)  PCDATA  $1, $0  0x0030 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, $-2  0x0042 00066 (main.go:10)  CMPL  runtime.writeBarrier(SB), $0  0x0049 00073 (main.go:10)  JEQ  77  0x004b 00075 (main.go:10)  JMP  86  0x004d 00077 (main.go:10)  MOVQ  $0, (AX)  0x0054 00084 (main.go:10)  JMP  103  0x0056 00086 (main.go:10)  MOVQ  AX, DI  0x0059 00089 (main.go:10)  XORL  DX, DX  0x005b 00091 (main.go:10)  NOP  0x0060 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


综合起来,代入我们自己写一个语言,感觉应该是:


  • 对于基础类型:int、char 等,可以直接将值放入栈中

  • 对于字符串、数组、类:栈中肯定是放不下的,那就放一个其存放的内存地址(或许这个对应 Java 的引用?)


知道操作系统的相关知识感觉是真有用啊,上面是目前学到的,但由此也有了很多疑问了:


  • 多个相同程序运行那地址是一样的,数据是如何进行隔离(有点映像,但细节当时是懵的,借助这个契机,后面研究研究)

  • 数组和类等具体是如何存取的,后面再仔细研究研究

参考链接

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

关注

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

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

评论

发布
暂无评论
自制操作系统番外:编程语言中变量是如何存储的_编程语言_萧_InfoQ写作社区