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

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

简介

在之前写操作系统的过程中,我们初步接触了一些寄存器和内存的基本概念,这篇将结合这些知识重新认识下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_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的

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月前
|
存储 算法
【操作系统】虚拟存储管理-页面置换算法
【操作系统】虚拟存储管理-页面置换算法
472 0
|
2月前
|
Unix 编译器 Shell
[oeasy]python0033_先有操作系统还是先有编程语言_c语言是怎么来的
本文回顾了计算机语言与操作系统的起源,探讨了早期 Unix 操作系统及其与 C 语言的相互促进发展。Unix 最初用汇编语言编写,运行在 PDP-7 上,后来 Thompson 和 Ritchie 开发了 C 语言及编译器,使 Unix 重写并成功编译。1974 年 Ritchie 发表论文,Unix 开始被学术界关注,并逐渐普及。伯克利分校也在此过程中发挥了重要作用,推动了 Unix 和 C 语言的广泛传播。
60 9
[oeasy]python0033_先有操作系统还是先有编程语言_c语言是怎么来的
|
4月前
|
Unix API 数据格式
云计算存储问题之API在不同操作系统上的实现如何解决
云计算存储问题之API在不同操作系统上的实现如何解决
|
5月前
|
IDE Oracle Java
Java 是一种跨平台的编程语言,可以在各种操作系统上运行。
Java 是一种跨平台的编程语言,可以在各种操作系统上运行。
|
6月前
|
存储 算法 固态存储
半导体存储品牌企业江波龙加入龙蜥社区,完成与 Anolis OS 适配
江波龙与 Anolis OS 的成功适配,进一步验证了 Anolis OS 能满足对存储容量灵活性和系统整体稳定性的高标准要求,可以为广大用户提供优质、高性能产品。
|
存储 算法 调度
操作系统实验五:存储管理设计
操作系统实验五:存储管理设计
263 0
|
6月前
|
存储 算法 大数据
认识操作系统段页式存储
存储管理负责高效地分配、管理和回收计算机的内存资源。这一过程对于确保计算机系统的稳定性、性能和可扩展性至关重要。
171 0
|
存储 缓存 Linux
实验 通过命令和代码初步感受存储管理【操作系统】
实验 通过命令和代码初步感受存储管理【操作系统】
131 0
|
6月前
|
开发框架 JavaScript 前端开发
鸿蒙操作系统支持哪些编程语言?
鸿蒙操作系统支持哪些编程语言?
226 0
|
6月前
|
存储 算法
操作系统:虚拟存储管理技术
操作系统:虚拟存储管理技术
143 0

热门文章

最新文章