写点什么

深入了解计算机语言

作者:邱学喆
  • 2022 年 10 月 04 日
    广东
  • 本文字数:14489 字

    阅读完需:约 48 分钟

深入了解计算机语言

一. 概述

大学期间有学 C 语言,自学汇编,java。唯独没有学 C++。毕业后就从事 java 应用开发,对底层没有过多思考,总结。借此机会,输出我对语言以及程序、进程的初浅的认知。便于后续学习新语言时,或者语言的高级特性学习,不再恐惧以及阻碍。

操作系统导论中,对程序进行抽象式的概论:程序=算法+结构;

那么会有以下疑惑?

  • 如何生成程序,经过哪些工序,才能形成程序 ?

  • 程序的结构是什么样的,或者说程序在计算机上的如何存储的?

  • 操作系统中的进程又是什么?

开始之前,我对语言、程序和进程的三者关系认知:

提前说明以下,汇编语言采用的是 AT&T 形式的,CPU 介绍的是因特尔处理器,下载资源

二. 计算机语言

这里是基于汇编语言来了解高级语言的底层逻辑;不会对汇编大量的介绍使用;只是把相关的一些指令所罗列出来,并不会过多进行介绍;

2.1 机器语言

当机器能识别的语言,也叫硬件语言,也就是由 0 和 1 组合而成,其控制电路在关键的线路进行开关操作,从而达到运算效果;

计算机系统在此基础上,抽象出一层指令的概念,来控制电路的运算;为此,我们无须关注底层硬件的逻辑;我们只需要操作这些指令即可。更多细节以及原理在汇编语言这个章节来介绍。

2.2 汇编语言

上面所介绍的指令,都是 01 组成,是很难辨别和记忆的;为此,大佬们给这些指令都分别敲定了不同的名称(助记符),还包含寄存器;

这里主要介绍的有关有关语言方面的通用指令集以及通用寄存器;不涉及操作系统层面上的寄存器,如 cr0~cr3,gdt,ldt,tss 等等

1. 寄存器

1.1. 通用寄存器

  • EAX , 用来做累加器和存放结果数据

  • EBX, 指向数据段的偏移量指针

  • ECX, 计数器

  • EDX, I/O 指针

  • ESI, 指向数据段的偏移量指针,也用来做批量操作场景下充当源开始位置

  • EDI, 用来做批量操作场景下充当源开始位置

  • ESP, 栈顶地址

  • EBP, 栈基地址

  • R8D ~R15D 64 位处理器新增的寄存器

1.2. 段寄存器

  • CS, 代码段

  • DS, 数据段;

  • SS, 栈段;

  • EIP, 当前代码段的下一个指令的偏移地址;

1.3. EFLAGS 寄存器

  • CF, 全称 carry flag 进位标志,如果算术操作指令,导致最高位发生了变化。例如加法进位,或者减法借位等则设置为 1;否则设置为 0;

  • PF, 全称 parity flag,奇偶位标志位;如果执行执行的结果中低 8 位的含有奇数个 1,则设置为 1,否则设置为 0;

  • ZF, 全称 zero flag 零标志,如果指令执行的结果为零时,会设置该标志位为 1,否则设置为 0;;

  • SF, 全称 sign flag 符号标志,如果执行执行结果的最高位为 1 时,则设置为 1,否则设置为 0;

  • OF, 全称 overflow flag 溢出标志,最近操作导致一个补码溢出,正溢出或负溢出;

2. 操作数寻址

这里提供了常规语言方面所使用到的寻址方式;

  • 立即数寻址,也就是常量值;

  • 寄存器寻址,也就是寄存器本身所记录的值;

  • 内存寻址,也就是访问内存地址中所存放的值:

  • 直接寻址,也叫做绝对寻址;即提供一个常量值,来代表内存地址

  • 间接寻址,寄存器所存放的值是代表内存地址;

  • 计算寻址,通过各种计算方式(如偏移量寻址等)来计算出内存地址;这里我自己归纳的;便以记忆;

这里所截 AT&T 语法格式的操作寻址表格:

3. 指令

3.1 数据传输指令

传输指令是将源操作数的值赋值给到目标操作数;



3.2 压入和弹出栈数据

3.3.算术和逻辑操作


3.5 跳转指令

3.6 过程调用指令

它指令与跳转指令有所相似,都是跳转目标代码地址去执行相关逻辑;

只是该指令做了额外的操作,会将当前下一个指令的地址放入栈中,以及返回时,进行出栈操作;

还有一个知识点:就是数据传递问题;由于寄存器数量不多,存放不了过程当中的局部变量以及参数,需要将这些内容放入到栈中;

额外的一个话题:在我用高级语言编写方法(也就是对应汇编语言中的过程),每个方法所使用多少内存栈,是通过编译器自己计算得出的。所以,要了解其规律,需要了解编译器的处理逻辑;

4. 汇编器

我们编写汇编文件,里面都是汇编语言,计算机是无法识别这些文本的形式的,那么就需要将其转换成二进制文件,计算机才能识别出这些。那么转换这个动作交由汇编器来处理,这个处理逻辑没有太负责,采用映射的形式,将各个关键字(助记符)替换成对应的 01 组成的指令。

工具主要是 binutils 所提供的 as 程序来处理。

5. 链接器

在日常的开发中,不会是一份文件写到从头写到尾的;而是具体落在各个汇编文件中;那么就需要这些文件进行整合。

这整合的动作,主要的将按照 ELF 组成结构,进行归整,以及符号重定位;这个章节不再介绍,而是留到程序章节一并讲解;

6. 总结

这里介绍的汇编的大体寄存器以及指令,并没有过多讲解汇编的原理以及各个指令的主要用途;需要额外的学习;在这里可以推荐我早期阅读的书籍《汇编语言》第 2 版,清华出版社出版,该语法格式采用 intel 语法。接着再看《深入理解计算机系统》。

但我们从上面的一些指令,例如跳转指令,过程调用,压栈和出栈等,能窥见高级语言的身影;

2.3 高级语言

除了机器语言和汇编语言之外,其余的编程语言都归为高级语言。这里主要介绍 C 语言。

我们在学习一门语言,都包含如下内容;

  • 数据类型

  • 变量

  • 运算符

  • 流程控制

  • if 条件控制

  • while、for 等循环

  • switch 多重分支

  • 过程

上面所列的是通用语言一般都会包括的特性,当然还有高级特性,例如对象,异常等。

这里将以汇编语言的方式来解读 C 的关键的语法格式,带着疑问去看。

1. 变量

在 c 语言中,处处都能看到变量名的使用。

而在汇编语言中,是没有变量名这么一说法,那么 c 语言中定义的变量在汇编语言中的是怎么样的运行逻辑。

变量只是一个指向一块存储区的名称,这个存储区可以有寄存器,以及内存;而内存呢,又分为栈内存,堆内存、特殊内存区域(如进程中数据内存区域)

  • 寄存器

int acc(int n){    int result = 0;    for(int i = 0;i<n;i++){        result += i;    }    return result;}//经过gcc -Og -S -std=gnu99 -S variable.c//去除汇编器、链接器的伪指令acc:		movl	$0, %edx //i = edx = 0	movl	$0, %eax //result = eax = 0	jmp	.L2.L3:	addl	%edx, %eax // result += i	addl	$1, %edx  //i += 1.L2:	cmpl	%edi, %edx //n = edi,n 与 i比较	jl	.L3 //小于跳转l3代码处	rep ret
复制代码

从上面的代码中,可以看出变量存储的区域是寄存器,需要额外的内存;同时也间接介绍了 eax 的用法,在累加以及返回结果时一般都会用 eax 来存放。

  • 栈内存

int stack(){    int i = 1;    int j = 2;    int k = 3;    int m = 4;    int n = 5;    int g = 6;    int result = i + j + k + m + n +g;    return result;}//gcc -S -std=gnu99 -S variable.cstack:	pushq	%rbp	movq	%rsp, %rbp 	movl	$1, -4(%rbp) //i = 1;	movl	$2, -8(%rbp) //j = 2;	movl	$3, -12(%rbp) //k = 3;	movl	$4, -16(%rbp) //m = 4	movl	$5, -20(%rbp) //n = 5;	movl	$6, -24(%rbp) //g = 6;	movl	-8(%rbp), %eax //eax = j = 2;	movl	-4(%rbp), %edx // edx = i = 1;	addl	%eax, %edx //edx = edx + eax = i + j = 3;	movl	-12(%rbp), %eax // eax = k = 3;	addl	%eax, %edx //edx = edx + eax = 3 + 3 = 6;	movl	-16(%rbp), %eax //eax = m = 4;	addl	%eax, %edx //edx = edx + eax = 6 + 4 = 10;	movl	-20(%rbp), %eax //eax = n = 5;	addl	%eax, %edx //edx = edx + eax = 10 + 5 = 15;	movl	-24(%rbp), %eax //eax = g = 6;	addl	%edx, %eax //eax = edx + eax = =15 + 6 = 21;	movl	%eax, -28(%rbp) //	movl	-28(%rbp), %eax	popq	%rbp	ret
复制代码

这里留有一个问题,为啥这个 stack 方法没有对 rsp 值进行减法处理;这块是由于编译器自身做了优化处理;但这并不影响程序的运转;下面就一个例子:

long swap_add(long * xp,long * yp){    long x = * xp;    long y = *yp;    *xp = y;    *yp = x;    return x+y;}long caller(){    long arg1 = 543;    long arg2 = 1057;    long sum = swap_add(&arg1,&arg2);    long diff = arg1 - arg2;    return sum * diff;}//gcc -Og -S call.c swap_add:	movq	(%rdi), %rdx //rdx=x=*xp=543;	movq	(%rsi), %rax //rax=y=*yp=1057;	movq	%rax, (%rdi) //*xp = y = 1057;	movq	%rdx, (%rsi) //*yp = x = 543;	addq	%rdx, %rax //rax = rax+rdx = 	retcaller:	subq	$16, %rsp //分配16字节的占内存,来存放arg1,arg2变量	movq	$543, 8(%rsp) //arg1 = 543	movq	$1057, (%rsp) //arg2 = 1057	movq	%rsp, %rsi //rsi = &arg2;	leaq	8(%rsp), %rdi //rdi = &arg1;	call	swap_add 	movq	8(%rsp), %rdx// rdx = arg1 = 543	subq	(%rsp), %rdx//diff = rdx = rdx - arg2 = arg1 - arg2 	imulq	%rdx, %rax //rax = sum * diff	addq	$16, %rsp //释放16字节栈内存	ret
复制代码

这个例子中,可以间接的看到指针的在计算机的表现形式。具体在指针小节中进行介绍;

  • 堆内存

一般是通过 malloc 等系统调用来分配内存。

int * init_array_4(){    int * array = malloc(sizeof(int)*4);    return array;}//init_array_4:	subq	$8, %rsp	movl	$16, %edi //通过sizeof(int)*4计算得出是16字节;	call	malloc	addq	$8, %rsp 	ret
复制代码
  • 特殊内存区域

2. 流程控制

2.1 条件控制

基本上是通过 test、cmp 与 jmp 系列指令相结合,来达到条件控制效果;

int condition_test(int i){    int j ;    if(i){        j = 2;    }else{        j = 3;    }    return j;}int condition_cmp(int i){    int j ;    if(i>10){        j = 2;    }else{        j = 3;    }    return j;}//condition_test:	testl	%edi, %edi	je	.L3  	movl	$2, %eax 	ret.L3:	movl	$3, %eax 	ret  condition_cmp:	cmpl	$10, %edi 	jle	.L6	movl	$2, %eax	ret.L6:	movl	$3, %eax	ret
复制代码

2.2 循环

循环也只是条件控制的变种而已;这里就不列出来了;

2.3 多重分支

switch 语法,本以为 switch 语法的底层原理是套用多个 if 的形式来达到效果的。但经过了解并不是,其实现方案有两种:

  • 使用多个条件控制来达到多重分支效果;

  • 采用跳转表机制,以空闲换时间来提高了运行速率;

int swith_if(int i){    int j;    switch(i){        case 4:            j = 4;            break;        case 8:             j = 8;            break;        case 12:            j = 12;            break;        default:            j = 1;    }    return j;}int switch_jt(int i){    int j;    switch(i){        case 4:             j = 4;            break;        case 8:            j = 8;            break;        case 12:            j = 12;            break;        case 16:            j = 16;            break;        case 20:            j = 20;            break;        case 24:            j = 24;            break;        case 28:            j = 28;            break;        default :            j = 1000;    }    return j;}///swith_if:	cmpl	$8, %edi	je	.L6	cmpl	$12, %edi	je	.L4	cmpl	$4, %edi	jne	.L7	movl	$4, %eax	ret.L4:	movl	$12, %eax	ret.L7:	movl	$1, %eax	ret.L6:	movl	$8, %eax	ret  switch_jt:	subl	$4, %edi	cmpl	$24, %edi	ja	.L9	movl	%edi, %edi	jmp	*.L11(,%rdi,8)//.L11:	.quad	.L10 //4	.quad	.L9 //5	.quad	.L9 //6	.quad	.L9 //7	.quad	.L18 //8	.quad	.L9 //9	.quad	.L9 //10	.quad	.L9 //11	.quad	.L13 //12	.quad	.L9 //13	.quad	.L9 //14	.quad	.L9 //15	.quad	.L14 //16	.quad	.L9 //17	.quad	.L9 //18	.quad	.L9 //19	.quad	.L15 //20	.quad	.L9 //21	.quad	.L9 //22	.quad	.L9 //23	.quad	.L16 //24	.quad	.L9 //25	.quad	.L9 //26	.quad	.L9 //27	.quad	.L17 //28	.text.L10:	movl	$4, %eax	ret.L13:	movl	$12, %eax	ret.L14:	movl	$16, %eax	ret.L15:	movl	$20, %eax	ret.L16:	movl	$24, %eax	ret.L17:	movl	$28, %eax	ret.L9:	movl	$1000, %eax	ret.L18:	movl	$8, %eax	ret
复制代码

虽然不知道什么情况下才会触发使用跳转表机制来实现。但我们从中了解到,在高级语言编写时,如果出现多个 ifelseif 等形式时,尽可能地采用 switch 来编写,这样子来提高运行效率

3. 函数

在汇编语言中,叫做过程,一般是通过 call 指令来达到调用;返回时使用 ret 指令来操作;

3.1 参数

  • 参数数量小于等于 6 个时,会直接用寄存器来存放参数

  • 大于 6 时,会采用栈内存来存放;

这个是编译器处理规则,或者说,不同地编译器也有可能不一样。我们知道有这么一回事即可;

这里主要讲地非明确地参数个数,也就是我们在高级语言中在地“可变参数”

这里就需要借用 c 的方法宏,在 stdarg.h 头文件定义,官方文档

  • va_start (va_list ap, preceding paraName) ; 第一个参数是 va_list 类型的实例,第二个参数是可变参数"..."的前一个参数名。通过这样子,我们可以确定可变参数的开始角标是多少。

  • va_arg (va_list ap, Type) 抵押给参数是 va_list 类型的实例,第二个是下一个参数的类型;返回值该类型的值

  • va_end(va_list ap) ,会清理 ap 实例的初始值;

稍微调整官方的例子:

#include <stdio.h>#include <stdarg.h> int add_nums_C99(int count, ...){    int result = 0;    va_list args;    va_start(args, count);      for (int i = 0; i < count; ++i) {        result += va_arg(args, int);    }     va_end(args);    return result;}  int main(void){    printf("%d\n", add_nums_C99(9, 25,1,2,3,4,5,6,7,8));}//add_nums_C99:	subq	$96, %rsp	movq	%rsi, -80(%rsp)	movq	%rdx, -72(%rsp)	movq	%rcx, -64(%rsp)	movq	%r8, -56(%rsp)	movq	%r9, -48(%rsp)	testb	%al, %al	je	.L2.L2:	movl	$8, -112(%rsp) //va_start(args, count);  我们知道取的是第几参数开始	movl	$48, -108(%rsp)		leaq	104(%rsp), %rax //第8个参数值,注意有可能存在缓存区溢出的可能性	movq	%rax, -104(%rsp)		leaq	-88(%rsp), %rax   //这个值是用来计算前面7个参数的辅助值	movq	%rax, -96(%rsp)		movl	$0, %edx	movl	$0, %eax	jmp	.L3	.L6:	movl	-112(%rsp), %ecx // ecx = 8n; 	cmpl	$48, %ecx // 比较参数个数	jnb	.L4   //处理前面七个参数的值	movl	%ecx, %esi  	addq	-96(%rsp), %rsi //计算参数对应的内存地址		addl	$8, %ecx //参数累加	movl	%ecx, -112(%rsp)		jmp	.L5.L4: //处理第六个参数 后面的参数 ; 参数> 6	movq	-104(%rsp), %rsi	leaq	8(%rsi), %rcx	movq	%rcx, -104(%rsp)	.L5: //=> for代码块	addl	(%rsi), %eax //result(eax) = result + (rsi) 【下一个参数值】	addl	$1, %edx.L3: => i(edx) < count(edi)	cmpl	%edi, %edx //  count < i 	jl	.L6 	addq	$96, %rsp	ret	main:	subq	$8, %rsp	movl	$50, %r8d	movl	$50, %ecx	movl	$25, %edx	movl	$25, %esi	movl	$4, %edi	movl	$0, %eax	call	add_nums_C99	movl	%eax, %esi	movl	$.LC0, %edi	movl	$0, %eax	call	printf	movl	$0, %eax	addq	$8, %rsp	ret
复制代码

同上汇编语言的分析,可以得知,可变参数是采用栈内存来实现的;那么是如何知道传了多少个数呢?答案是不知道的,只能通过前面的参数来确定参数个数才行;

额外的话题,没有高级语言的对照来看,来真不太好快速分析它的实现逻辑;非常耗时;

3.2 内存管理

方法中的定义的变量(除 malloc 等系统调用)是使用栈内存,其是通过 rsp 栈指针调整,从而达到内存管理的;这里可能要留意一下,如果定义变量没有给其初始值,那么对应的栈内存是什么值,该变量就是什么值;

4. 指针

在上面的例子中,或多或少有看到指针的使用场景;

指针是指向内存地址中所存放的值。

C 语言中提供了两个符号来处理指针的相关事宜;

  • ”&“ 取该变量的地址;

  • ”*“取该地址指向的值。

#include<stdio.h>int main(){    int i = 1;    int * j = &i;    int **k = &j;    printf("i=%ld,j=%ld,k=%ld\n",i,*j,**k);    return i;}//main:	pushq	%rbp	movq	%rsp, %rbp	subq	$32, %rsp	movl	$1, -12(%rbp) //i = 1	leaq	-12(%rbp), %rax //&i	movq	%rax, -24(%rbp) //*j = &i	leaq	-24(%rbp), %rax  //&j	movq	%rax, -8(%rbp) //**k = &j	movq	-8(%rbp), %rax	movq	(%rax), %rax	movl	(%rax), %ecx	movq	-24(%rbp), %rax	movl	(%rax), %edx	movl	-12(%rbp), %eax	movl	%eax, %esi	movl	$.LC0, %edi	movl	$0, %eax	call	printf	movl	-12(%rbp), %eax	leave	ret
复制代码

5. 总结

经过上面的介绍,对使用 C 语言来进行开发不不会有太多问题了。但离复杂的系统开发还有很大的距离。如何将 C 语言程序文件,编译成程序。这个在程序这个章节来介绍。


三. 语言进阶

市面上还有 C++、java 等开发语言,其里面提供了一个高级特性,对象编程。

那么就针对对象这一特性进行介绍。

3.1 面向对象

对象有三个特性,如下:

  • 封装——数据和行为包装成一个类(独立模块)——》隐藏

  • 继承——类与类之间的关系,通过继承关系形成父类与关系——》提高代码复用性

  • 多态——同一个行为具有不同表现形式——》运行时

在上面的高级语言中对汇编语言以及 C 语言的介绍,了解到计算机是面向过程编程的。那么面向对象编程是如何借用过程编程的原理来实现对象编程呢?由于这篇不对编译器底层逻辑进行刨析,而是直接通过编译后得到的汇编文件进行解读;所以提前公布这么一个 答案,通过特殊编译器,进行对【对象的解析、编译】转成汇编文件。也就是说,面向对象语言经过编译器编译后得到面向过程的汇编文件。


并不会直接来介绍三个特性原理,而是通过汇编语言的角度来触发解读这三大特性。

在内存中该对象的数据模型是什么样的。-》封装了

继承时,数据又是什么样子的?-》继承

如何使用虚拟表 -》多态

额外 C++特性,构造函数和析构函数。

待后续再补充。

现在以 C++语言的例子来解读编译后的汇编文件。

3.1.1 封装

一个对象大体包含两个部分

  • 成员属性

  • 静态属性,也叫类属性

  • 非静态属性,也叫对象属性

  • 成员函数

  • 静态函数,也叫类函数

  • 非静态函数,也叫对象函数

  • 虚函数

#include<stdio.h>
class Demo{ private: int i; static int j; public: static int k; Demo(int i){ this->i = i; }; int getI(){ return this->i; }; static int getJ(){ return j; }; static void setJ(int m){ j = m; }; };int Demo::j=30;int Demo::k;int main(){ Demo * a = new Demo(10); printf("i=%d\n",a->getI()); printf("j=%d\n",Demo::getJ()); Demo::setJ(40); printf("j=%d\n",Demo::getJ()); Demo::k=500; printf("k=%d\n",Demo::k); delete a; return 0;}
复制代码


####构造函数########_ZN4DemoC2Ei:	pushq	%rbp	movq	%rsp, %rbp	movq	%rdi, -8(%rbp)	movl	%esi, -12(%rbp)	movq	-8(%rbp), %rax	movl	-12(%rbp), %edx	movl	%edx, (%rax)	popq	%rbp	ret.LFE3:	.set	_ZN4DemoC1Ei,_ZN4DemoC2Ei###getI函数_ZN4Demo4getIEv:	pushq	%rbp	movq	%rsp, %rbp	movq	%rdi, -8(%rbp)	movq	-8(%rbp), %rax	movl	(%rax), %eax	popq	%rbp	ret###getJ函数_ZN4Demo4getJEv:	pushq	%rbp	movq	%rsp, %rbp	movl	_ZN4Demo1jE(%rip), %eax	popq	%rbp	ret###setJ函数_ZN4Demo4setJEi:	pushq	%rbp	movq	%rsp, %rbp	movl	%edi, -4(%rbp)	movl	-4(%rbp), %eax	movl	%eax, _ZN4Demo1jE(%rip)	popq	%rbp	ret###静态变量j_ZN4Demo1jE:	.long	30###静态变量k_ZN4Demo1kE:	.zero	4.LC0:	.string	"i=%d\n".LC1:	.string	"j=%d\n".LC2:	.string	"k=%d\n"	main:	pushq	%rbp	movq	%rsp, %rbp	pushq	%rbx	subq	$24, %rsp	###分配4字节的内存	movl	$4, %edi	call	_Znwm	###调用 Demo构造函数	movq	%rax, %rbx	movl	$10, %esi	movq	%rbx, %rdi	call	_ZN4DemoC1Ei	###将a对象的地址存放到-24(%rbp)	movq	%rbx, -24(%rbp)	###调用getI函数	movq	-24(%rbp), %rax	movq	%rax, %rdi	call	_ZN4Demo4getIEv	###打印i=%d\n	movl	%eax, %esi	movl	$.LC0, %edi	movl	$0, %eax	call	printf	###调用静态getJ函数	call	_ZN4Demo4getJEv	###打印j=%d\n	movl	%eax, %esi	movl	$.LC1, %edi	movl	$0, %eax	call	printf	###调用Demo::setJ	movl	$40, %edi	call	_ZN4Demo4setJEi	###调用Demo::getJ	call	_ZN4Demo4getJEv	###打印j=%d\n	movl	%eax, %esi	movl	$.LC1, %edi	movl	$0, %eax	call	printf	###Demo::k=500	movl	$500, _ZN4Demo1kE(%rip)	#打印k=%d\n	movl	_ZN4Demo1kE(%rip), %eax	movl	%eax, %esi	movl	$.LC2, %edi	movl	$0, %eax	call	printf	//......	movl	$0, %eax	addq	$24, %rsp	popq	%rbx	popq	%rbp	ret
复制代码

将其转成 C 语言的伪代码,如下:(当然还有其他表现形式)

int Demo::j = 30;int Demo::k;
struct Demo{ int i;};
construct_Demo(struct Demo * a, int i){ a->i = i;}int getI(struct Demo * a){ return a->i;}int Demo::getJ(){ return Demo::j;}void Demo::setJ(int i){ Demo::j = j;}
int main(){ struct Demo * a = malloc(sizeof(struct Demo)); construct_Demo(a,10); printf("i=%d\n",getI(a)); printf("j=%d\n",Demo::getJ()); Demo::setJ(40); printf("j=%d\n",Demo::getJ()); Demo::k=500; printf("k=%d\n",Demo::k); delete a; return 0;}
复制代码

3.1.2 继承

#include<stdio.h>
class A{ private: int a; public: A(int a){ this->a=a; }; int getA(){ return this->a; };};class B: public A{ private: int b; public: B(int b,int a ):A(a){ this->b = b; }; int getB(){ return this->b; };};
class C: public A{ private : int c; public: C(int c,int a):A(a){ this->c = c; }; int getC(){ return this->c; };};
class D: public B, public C{ private: int d; public: D(int d,int c,int b,int a):B(b,b),C(c,c){ this->d = d; }; int getD(){ return this->d; };};
int main(){ D* d= new D(40,30,20,10); printf("a=%d,b=%d,c=%d,d=%d\n",d->C::getA(),d->getB(),d->getC(),d->getD()); return 0;}
复制代码


###A类构造函数_ZN1AC2Ei:	pushq	%rbp	movq	%rsp, %rbp	###将参数保存到栈中	movq	%rdi, -8(%rbp)	movl	%esi, -12(%rbp)	###赋值a变量	movq	-8(%rbp), %rax	movl	-12(%rbp), %edx	movl	%edx, (%rax)	popq	%rbp	ret.LFE1:	.set	_ZN1AC1Ei,_ZN1AC2Ei###int A::getA()_ZN1A4getAEv:	pushq	%rbp	movq	%rsp, %rbp	movq	%rdi, -8(%rbp)	movq	-8(%rbp), %rax	movl	(%rax), %eax	popq	%rbp	ret###B类构造函数_ZN1BC2Eii:	pushq	%rbp	movq	%rsp, %rbp	subq	$16, %rsp	###将参数保存到栈中	movq	%rdi, -8(%rbp)	movl	%esi, -12(%rbp)	movl	%edx, -16(%rbp)	###调用A类构造函数	movq	-8(%rbp), %rax	movl	-16(%rbp), %edx	movl	%edx, %esi	movq	%rax, %rdi	call	_ZN1AC2Ei	###赋值b变量	movq	-8(%rbp), %rax	movl	-12(%rbp), %edx	movl	%edx, 4(%rax)	leave	ret.LFE5:	.set	_ZN1BC1Eii,_ZN1BC2Eii####B::int getB()_ZN1B4getBEv:	pushq	%rbp	movq	%rsp, %rbp	movq	%rdi, -8(%rbp)	movq	-8(%rbp), %rax	movl	4(%rax), %eax	popq	%rbp	ret####C类构造函数_ZN1CC2Eii:	pushq	%rb	movq	%rsp, %rbp	subq	$16, %rsp	###保存入参到栈中	movq	%rdi, -8(%rbp)	movl	%esi, -12(%rbp)	movl	%edx, -16(%rbp)	###调用A类构造函数	movq	-8(%rbp), %rax	movl	-16(%rbp), %edx	movl	%edx, %esi	movq	%rax, %rdi	call	_ZN1AC2Ei	###赋值c变量	movq	-8(%rbp), %rax	movl	-12(%rbp), %edx	movl	%edx, 4(%rax)	leave	ret.LFE9:	.set	_ZN1CC1Eii,_ZN1CC2Eii###C::getC()函数_ZN1C4getCEv:	pushq	%rbp	movq	%rsp, %rbp	movq	%rdi, -8(%rbp)	movq	-8(%rbp), %rax	movl	4(%rax), %eax	popq	%rbp	ret###D类构造函数_ZN1DC2Eiiii:	pushq	%rbp	movq	%rsp, %rbp	subq	$32, %rsp	##将参数存放到栈中	movq	%rdi, -8(%rbp)	movl	%esi, -12(%rbp)	movl	%edx, -16(%rbp)	movl	%ecx, -20(%rbp)	movl	%r8d, -24(%rbp)	###调用B类构造函数	movq	-8(%rbp), %rax	movl	-20(%rbp), %edx	movl	-20(%rbp), %ecx	movl	%ecx, %esi	movq	%rax, %rdi	call	_ZN1BC2Eii	###调用C类构造函数	movq	-8(%rbp), %rax	leaq	8(%rax), %rcx  ###注意.指针偏移	movl	-16(%rbp), %edx	movl	-16(%rbp), %eax	movl	%eax, %esi	movq	%rcx, %rdi	call	_ZN1CC2Eii	###赋值d变量	movq	-8(%rbp), %rax	movl	-12(%rbp), %edx	movl	%edx, 16(%rax)	leave	ret.LFE13:	.set	_ZN1DC1Eiiii,_ZN1DC2Eiiii###D::getD函数_ZN1D4getDEv:	pushq	%rbp	movq	%rsp, %rbp	movq	%rdi, -8(%rbp)	movq	-8(%rbp), %rax	movl	16(%rax), %eax	popq	%rbp	ret.LC0:	.string	"a=%d,b=%d,c=%d,d=%d\n"main:	pushq	%rbp	movq	%rsp, %rbp	pushq	%r13	pushq	%r12	pushq	%rbx	subq	$24, %rsp	###分配20字节的内存	movl	$20, %edi	call	_Znwm	###调用D构造函数进行初始化	movq	%rax, %rbx	movl	$10, %r8d	movl	$20, %ecx	movl	$30, %edx	movl	$40, %esi	movq	%rbx, %rdi	call	_ZN1DC1Eiiii	###将d对象的地址保存到-40(%rbp)	movq	%rbx, -40(%rbp)	###d->getD()	movq	-40(%rbp), %rax	movq	%rax, %rdi	call	_ZN1D4getDEv		##将d->getD()保存到%r13d	movl	%eax, %r13d	###d->getC()	movq	-40(%rbp), %rax	addq	$8, %rax  ###***注意,指针偏移;	movq	%rax, %rdi	call	_ZN1C4getCEv	##将d->getC()保存到%r12d	movl	%eax, %r12d		###d->getB()	movq	-40(%rbp), %rax	movq	%rax, %rdi	call	_ZN1B4getBEv	##将d->getB()保存到%ebx	movl	%eax, %ebx	###d->getA()	movq	-40(%rbp), %rax	addq	$8, %rax	movq	%rax, %rdi	call	_ZN1A4getAEv	#打印a=%d,b=%d,c=%d,d=%d\n	movl	%r13d, %r8d	movl	%r12d, %ecx	movl	%ebx, %edx	movl	%eax, %esi	movl	$.LC0, %edi	movl	$0, %eax	call	printf		movl	$0, %eax	addq	$24, %rsp	popq	%rbx	popq	%r12	popq	%r13	popq	%rbp	ret
复制代码

转成 C 语言的代码如下:

struct C{    struct A a;    int c;};construct_C(struct C* this, int c,int a){    construct_A((struct A*)&this->a,a);    this->c = c;};int getC(struct C*  this){    return this->c;};struct D{    struct B b;    struct C c;    int d;};construct_D(struct D* this, int d, int c, int b,int a){    construct_B(&this->b,b,b);    construct_C(&this->c,c,c);    this->d = d;};int getD(struct D*  this){    return this->d;};int main(){    struct D * d = malloc(sizeof(struct D));    construct_D(d,40,30,20,10);    printf("a=%d,b=%d,c=%d,d=%d\n",getA((struct A*)&d->c.a),getB((struct B *)&d->b),getC((struct C *)&d->c),getD(d));    return 0;};
复制代码

3.1.3 多态

#include<stdio.h>
class A{ public: virtual void display(){ printf("A class display....\n"); };};class B: public A{ public: void display(){ printf("B class display....\n"); };}; void display(A * a){ printf("invoke display method....\n"); a->display();}
int main(){ A *a = new B(); A *b = new A(); display(a); display(b);
delete a; delete b; return 0;}
复制代码


==========A.display()==========.LC0:	.string	"A class display...."_ZN1A7displayEv:	pushq	%rbp	movq	%rsp, %rbp	subq	$16, %rsp	movq	%rdi, -8(%rbp)	movl	$.LC0, %edi	call	puts	leave	ret	==========B.display()============.LC1:	.string	"B class display...."	_ZN1B7displayEv:	pushq	%rbp	movq	%rsp, %rbp	subq	$16, %rsp	movq	%rdi, -8(%rbp)	movl	$.LC1, %edi	call	puts	leave	ret==========display()	===========.LC2:	.string	"invoke display method...."	_Z7displayP1A:	pushq	%rbp	movq	%rsp, %rbp	subq	$16, %rsp	##打印invoke display method.....	movq	%rdi, -8(%rbp)	movl	$.LC2, %edi	call	puts	##通过虚函数表找到对应的函数地址	movq	-8(%rbp), %rax	movq	(%rax), %rax	movq	(%rax), %rax  ###调用函数地址	movq	-8(%rbp), %rdx  	movq	%rdx, %rdi	call	*%rax		leave==========A构造函数==========_ZN1AC2Ev:	pushq	%rbp	movq	%rsp, %rbp		###将A类的成员函数display(_ZN1A7displayEv)的地址保存到对象的【虚函数表】中	movq	%rdi, -8(%rbp)	movq	-8(%rbp), %rax	movq	$_ZTV1A+16, (%rax)	popq	%rbp	ret.LFE6:	.set	_ZN1AC1Ev,_ZN1AC2Ev==========B构造函数==========	_ZN1BC2Ev:	pushq	%rbp	movq	%rsp, %rbp	subq	$16, %rsp	####	movq	%rdi, -8(%rbp)	###调用A构造函数	movq	-8(%rbp), %rax	movq	%rax, %rdi	call	_ZN1AC2Ev	###将B类的成员函数display(_ZN1B7displayEv)的地址保存到对象的【虚函数表】中	movq	-8(%rbp), %rax	movq	$_ZTV1B+16, (%rax)	leave	ret.LFE8:	.set	_ZN1BC1Ev,_ZN1BC2Ev==========main函数==========		main:	pushq	%rbp	movq	%rsp, %rbp	pushq	%rbx	subq	$24, %rsp	#####分配8字节内存	movl	$8, %edi	call	_Znwm	####调用B构造函数	movq	%rax, %rbx	#######将a对象指向的内存区域清零	movq	$0, (%rbx)	movq	%rbx, %rdi	call	_ZN1BC1Ev	##将a对象的地址保存到栈中-24(%rbp)	movq	%rbx, -24(%rbp)	#####分配8字节内存	movl	$8, %edi	call	_Znwm	####调用A构造函数	movq	%rax, %rbx	movq	$0, (%rbx)	movq	%rbx, %rdi	call	_ZN1AC1Ev	##将b对象的地址保存到栈中-32(%rbp)	movq	%rbx, -32(%rbp)	###调用display方法	movq	-24(%rbp), %rax	movq	%rax, %rdi	call	_Z7displayP1A	###调用display方法	movq	-32(%rbp), %rax	movq	%rax, %rdi	call	_Z7displayP1A	##调用delete释放内存	movq	-24(%rbp), %rax	movq	%rax, %rdi	call	_ZdlPv  ##调用delete释放内存	movq	-32(%rbp), %rax	movq	%rax, %rdi	call	_ZdlPv	movl	$0, %eax	addq	$24, %rsp	popq	%rbx	popq	%rbp	ret##B类虚函数表	_ZTV1B:	.quad	0	.quad	_ZTI1B	.quad	_ZN1B7displayEv##A类虚函数表_ZTV1A:	.quad	0	.quad	_ZTI1A	.quad	_ZN1A7displayEv_ZTS1B:	.string	"1B"_ZTI1B:	.quad	_ZTVN10__cxxabiv120__si_class_type_infoE+16	.quad	_ZTS1B	.quad	_ZTI1A_ZTS1A:	.string	"1A"_ZTI1A:	.quad	_ZTVN10__cxxabiv117__class_type_infoE+16	.quad	_ZTS1A
复制代码

转成 C 语言的代码如下:

#include<stdio.h>#include<stdlib.h>struct A{    void * v_methods;};struct a_v_methods{    long  reserve;    void * type;    void (* display)(struct A *a);};struct B{    void * v_methods;};struct b_v_methods{    long  reserve;    void * type;    void (* display)(struct B * b);};void A_display(){    printf("A class display....\n");};void B_display(){    printf("B class display....\n");};
char A_Str[3] = "1A\0";char B_Str[3] = "1B\0";char A_type[1] = "A";char B_type[1] = "B";
struct b_v_methods b_v = { 0,&B_Str,&B_display};struct a_v_methods a_v = { 0,&A_Str,&A_display};
void construct_A(struct A *a){ a->v_methods =&a_v.display;//关键的代码};void construct_B(struct B *b){ construct_A((struct A *)b); b->v_methods = &b_v.display;//关键的代码};void display(struct A * a){ void ** method_ptr = a->v_methods; ////关键的代码 void(*display)(struct A* ) = (void (* )(struct A *))( *method_ptr);//关键的代码 display(a); };int main(){ struct A * a = malloc(sizeof(struct B)); construct_B((struct B *)a); struct A * b = malloc(sizeof(struct A)); construct_A(b); display(a); display(b); free(a); free(b); return 0;}
复制代码

3.1.4 总结

所谓的面向对象编程,其本质还是面向过程编程。唯独的差异是编译器的处理逻辑不一样。我们依然通过面向过程编程,也能达到面向对象编程效果;至于对象中的私有,保护、共有等修饰符,也只是在编译过程中对其代码调用进行约束而已;

3.1.5 Java

java 语言所编写的程序是运行在虚拟机上的一门面向对象语言;开源的虚拟机 JVM,也是用了 C++语言所编写的;由于本人对 openjdk 的 jvm 的源码阅读不深,以下的见解仅供参考;

  • Java 与 C++的区别

  • 对象的内存存放区域,Java 对象保存在堆中,C++对象可保存到堆中,也可以保存到栈中;

  • 面向对象的语法限制,Java 只能单一继承且是虚拟共有继承,C++支持多继承,支持形式多样的继承方式;

  • 内存回收,java 提供垃圾回收器,而 C++不提供内存回收机制;

  • 类加载——采用了特殊的动态链接器来实现动态加载;

  • 对象、方法、属性——java 中的各种类型,在 jvm 中都有对应的类容器来装载。例如 Java 类以及对象容器是保存到 C++对应的类中,普通 Java 对象,分别保存到 InstanceKlass 类以及 instanceOopDesc 类中;

由于 java 的实现逻辑是基于 JVM,而 JVM 是采用的 C++语言来实现的;待后续研究其 JVM 的实现逻辑后,再来介绍;这里先罗列 JVM 的几大组成部分;

3.2 响应式编程

近几年市面上兴起一个概念,响应式编程,也叫做流编程。其本质是积木式的堆叠处理逻辑,或者说是流水线的处理方式。当有数据产生或者事件产生,那么就会流经积木上的所有模块后得到的最终的数据;各个模块,可以理解为函数体,或者说是对象;

之所以盛行,主要是其编写的应用代码相对于美观。然而也有弊端:性能在一定程序上有所损耗;同时给问题定位排查等带来难度。

有关 java 方面的响应式编程,可以查看《RxJava》开源框架,spring webflux,以及 jdk8 提供的 lamba 表达式。

这里不再介绍。

四. 总结

从机器语言、汇编语言、到高级语言的相关介绍,得知编程语言的原理。市面上的各种编程语言形式多样,特点也会有所不一样,都是依赖其语言所提供的一套编译器来实现的。所以其再怎么变,特性再新增,也是最终生成机器指令,或者说汇编文件,所以底层硬件的语言没变,上层的高级语言再怎么变也脱离不开硬件的限制。所以,高级语言的特性以及优势,主要注重点如下:

  • 业务代码量

  • 代码的美观

由于在职业生涯中,并没有从事有关汇编语言的以及 C、C++语言的开发工作,这些也只是我大学期间,毕业后所阅读开源系统时所学习的语言,并没有形成一套完整的知识体系,待后续深入了解,形成一套完整的知识体系后,再分别着重介绍。

而之所以写这篇文章,一方面是以前所了解的语言进行总结;另一方面,也输出我对编程语言的理解;

中心思想是:不在恐惧新编程语言,也就那么一回事。

引用

深入理解计算机系统

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

邱学喆

关注

签约作者;计算机原理的深度解读,源码分析 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论

发布
暂无评论
深入了解计算机语言_响应式编程_邱学喆_InfoQ写作社区