一. 概述
大学期间有学 C 语言,自学汇编,java。唯独没有学 C++。毕业后就从事 java 应用开发,对底层没有过多思考,总结。借此机会,输出我对语言以及程序、进程的初浅的认知。便于后续学习新语言时,或者语言的高级特性学习,不再恐惧以及阻碍。
操作系统导论中,对程序进行抽象式的概论:程序=算法+结构;
那么会有以下疑惑?
开始之前,我对语言、程序和进程的三者关系认知:
提前说明以下,汇编语言采用的是 AT&T 形式的,CPU 介绍的是因特尔处理器,下载资源。
二. 计算机语言
这里是基于汇编语言来了解高级语言的底层逻辑;不会对汇编大量的介绍使用;只是把相关的一些指令所罗列出来,并不会过多进行介绍;
2.1 机器语言
当机器能识别的语言,也叫硬件语言,也就是由 0 和 1 组合而成,其控制电路在关键的线路进行开关操作,从而达到运算效果;
计算机系统在此基础上,抽象出一层指令的概念,来控制电路的运算;为此,我们无须关注底层硬件的逻辑;我们只需要操作这些指令即可。更多细节以及原理在汇编语言这个章节来介绍。
2.2 汇编语言
上面所介绍的指令,都是 01 组成,是很难辨别和记忆的;为此,大佬们给这些指令都分别敲定了不同的名称(助记符),还包含寄存器;
这里主要介绍的有关有关语言方面的通用指令集以及通用寄存器;不涉及操作系统层面上的寄存器,如 cr0~cr3,gdt,ldt,tss 等等
1. 寄存器
1.1. 通用寄存器
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 参数
这个是编译器处理规则,或者说,不同地编译器也有可能不一样。我们知道有这么一回事即可;
这里主要讲地非明确地参数个数,也就是我们在高级语言中在地“可变参数”
这里就需要借用 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++语言的开发工作,这些也只是我大学期间,毕业后所阅读开源系统时所学习的语言,并没有形成一套完整的知识体系,待后续深入了解,形成一套完整的知识体系后,再分别着重介绍。
而之所以写这篇文章,一方面是以前所了解的语言进行总结;另一方面,也输出我对编程语言的理解;
中心思想是:不在恐惧新编程语言,也就那么一回事。
引用
深入理解计算机系统
评论