当前位置: 首页 > article >正文

高级编程语言的基本语法在CPU的眼中是什么样的呢?

任何一门高级编程语言,就一定存在下面这几个语法元素

  1. 变量
  2. 类型
  3. 数组
  4. 控制语句(条件,循环)
  5. 运算符(算术运算,布尔运算,赋值运算,关系运算,位运算)
  6. 函数

而本节探究的是,这6个语法元素在CPU的眼中是什么样子的呢?我们先来看看变量。

说到变量我们的先从内存说起,为了方便管理,整个内存被划分为一块一块的,我们把这样一块的内存叫做内存单元,通常情况下,一块内存单元的大小为一个字节,我们需要给这些内存单元编号,从0开始,而这个编号有个专门的名字,叫做内存地址。CPU比较偏爱内存地址,因为知道内存地址就可以操作对应的内存单元。但是我们并不喜欢内存地址,因为内存地址是一串数字,没有任何可读性,于是我们映入变量的概念,变量就是这块内存单元的别名。一个比较合适的类比:变量与内存地址的关系和域名与IP地址的关系一样。比如下面这两段代码

#include <stdio.h>
int main() {
    int a = 1;
    return 0;
}
main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 1 ; 这里就是 int a = 1;
        mov     eax, 0
        pop     rbp
        ret

接下来我们来谈谈类型,类似其实有两个作用,对于我们开发者而言,必要的类型检验可以帮我我们减少代码错误。对于CPU而言,类型指定了操作数的大小。比如下面这两段代码:

#include <stdio.h>
int main() {
    int num1 = 1;
    long num2 = 100;
    return 0;
}
main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 1 ; int num1 = 1;
        mov     QWORD PTR [rbp-16], 100 ; long num2 = 100;
        mov     eax, 0
        pop     rbp
        ret

DWORD 表示操作4个内存单元,QWORD 表示操作8个内存单元

基本上每个编程语言都提供了数组这个基础的数据结构,为什么呢?因为现实世界需要,因为有这样的需求。通常意义上,数组是存储多个同类型的数据结构,这意味这他的内存结构是连续的。所以对于CPU而言,他不过是一块连续的内存单元而已。

控制语句可以说是编程语言的灵魂,全部的程序都是由条件语句,循环语句这样像搭积木一样搭建出来的。而这些控制语句在CPU的眼中,不过是几条固定的指令。

#include <stdio.h>

int main() {
    int a = 10;
    int b = 9;
    if (a > b) {
        printf("a more than b");
    }else {
        printf("b more than a");
    }
}
.LC0:
        .string "a more than b"
.LC1:
        .string "b more than a"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 10
        mov     DWORD PTR [rbp-8], 9
        mov     eax, DWORD PTR [rbp-4]
        cmp     eax, DWORD PTR [rbp-8]
        jle     .L2
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        jmp     .L3
.L2:
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 0
        call    printf
.L3:
        mov     eax, 0
        leave
        ret

可以发现控制语句对应的指令就是 jxx 循环语句也是一样的,只不过不是跳转的位置不是往后,而是往前。

#include <stdio.h>

int main() {
    int sum = 0;
    for (int i = 0; i<= 100; i++) {
        sum += i;
    }
}
main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 0
        mov     DWORD PTR [rbp-8], 0
        jmp     .L2
.L3:
        mov     eax, DWORD PTR [rbp-8]
        add     DWORD PTR [rbp-4], eax ; sum += i;
        add     DWORD PTR [rbp-8], 1 ; i++
.L2:
        cmp     DWORD PTR [rbp-8], 100 ; i <= 100;
        jle     .L3
        mov     eax, 0
        pop     rbp
        ret

高级语言中的运算符就更加不用说了,不过是一些运算指令。到此编写一个程序所需要的全部语法在CPU层面都已经解构完毕了,而函数不过是一种让程序模块化的最基本的手段。方便我们在编写庞大,复杂的程序时,能够更加简单,更加灵活。那么函数在CPU的眼中是什么样子的呢?

函数的出现,让变量的生命周期(也叫作用域)有了区别,函数内部的变量会随着函数的调用而创建,函数的返回而销毁。这样做的目的是充分利用内存。接下来我们通过一个例子来看看函数是如何被调用的,又是如何被返回的。

#include <stdio.h>

int f1(int num) {
    int max = 100;
    return num + max;
}

int main() {
    int init = 10;
    int res = f1(init);
    return 0;
}
f1:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-20], edi
        mov     DWORD PTR [rbp-4], 100
        mov     edx, DWORD PTR [rbp-20]
        mov     eax, DWORD PTR [rbp-4]
        add     eax, edx
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 10
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    f1
        mov     DWORD PTR [rbp-8], eax
        mov     eax, 0
        leave
        ret

可以发现,调用函数会使用call指令,这个指令的作用是将下一条的指令入栈,然后跳转到f1代码段,在每个函数的开头都有这两行指令,push rbp mov rbp, rsp 这两条指令的作用是,先保存上一个函数的栈帧起点,然后重置栈帧的起点为当前的栈顶,即创建一个新的栈帧。然后再存放局部变量。然后函数结束,pop rbp ret 执行这两条指令,rbp寄存器回到上一个函数的栈帧的起点,ret指令让指令寄存器IP,回到调用函数的位置继续执行。

到此,相信你一定有所体会,CPU很呆板,只会按照我们写好的指令一条一条的执行。我们可以看到比较高级的语法,例如函数调用其实不是CPU本身就支持,而是我们通过一些额外的指令让CPU可以做到函数调用,而这些额外的指令都是编译器生成的。所以我们常常说一个语言是否支持某种语言特性,取决于它的编译器


http://www.kler.cn/a/508242.html

相关文章:

  • 基于SSM汽车美容管家【提供源码+答辩PPT+文档+项目部署】(高质量源码,可定制,提供文档,免费部署到本地)
  • LabVIEW串口通信调试与数据接收问题
  • 代码随想录算法训练营第三十五天-动态规划-01背包(二维)
  • ASP.NET Core - 配置系统之配置添加
  • 高级编程语言的基本语法在CPU的眼中是什么样的呢?
  • 【论文阅读笔记】基于YOLO和ResNet深度卷积神经网络的结直肠息肉检测
  • javaEE初阶————多线程初阶(2)
  • 混淆矩阵 confusion matrix 怎么看 -- 识别涉黄图片的二分类为例 - 识别毒品的多分类案例解释
  • Freeswitch使用media_bug能力实现回铃音检测
  • [创业之路-249]:《华为流程变革:责权利梳理与流程体系建设》核心内容
  • 【GIS系列】打造3维GIS数字孪生效果系统:Cesium+Mapbox+SpringBoot完美实现解析
  • Spring Boot--@PathVariable、@RequestParam、@RequestBody
  • 如何使用 Go语言操作亚马逊 S3 对象云存储
  • 【Cesium入门教程】第一课:Cesium简介与快速入门详细教程
  • 机器学习——集成学习、线性模型、支持向量机、K近邻、决策树、朴素贝叶斯、虚拟分类器分析电动车数据集Python完整代码
  • boss直聘 __zp_stoken__ 分析
  • 【Unity3D】远处的物体会闪烁问题(深度冲突) Reversed-Z
  • 【Go】Go Gorm 详解
  • Ardupilot开源无人机之Geek SDK进展2024
  • ThinkPHP 8的一对多关联
  • 花样贪吃蛇
  • (即插即用模块-Attention部分) 四十四、(ICIP 2022) HWA 半小波注意力
  • DevUI 2024 年度运营报告:开源生态的成长足迹与未来蓝图
  • vue v-if和key值的注意的地方
  • 跨站请求伪造(CSRF)介绍
  • 多监控m3u8视频流,怎么获取每个监控的封面图(纯前端)