一. 概述
大学期间有学 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.c
stack:
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 =
ret
caller:
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++语言的开发工作,这些也只是我大学期间,毕业后所阅读开源系统时所学习的语言,并没有形成一套完整的知识体系,待后续深入了解,形成一套完整的知识体系后,再分别着重介绍。
而之所以写这篇文章,一方面是以前所了解的语言进行总结;另一方面,也输出我对编程语言的理解;
中心思想是:不在恐惧新编程语言,也就那么一回事。
引用
深入理解计算机系统
评论