哈工大计算机系统大作业-C、C++底层实现分析

哈工大计算机系统大作业-C、C++底层实现分析

  • 第1章 C语言的语言元素
    • 1.1 程序结构
      • 1.1.1 循序结构
      • 1.1.2 分支
        • (1)if语句
        • (2)switch语句
      • 1.1.3 循环
        • (1)while语句
        • (2)do-while语句
        • (3)for语句
    • 1.2 变量
      • 1.2.1 全局变量
      • 1.2.2 局部变量
      • 1.2.3 整型变量
      • 1.2.4 浮点变量
      • 1.2.5 寄存器变量
      • 1.2.6 指针变量
      • 1.2.7 结构体
      • 1.2.8 共用体
      • 1.2.9 枚举类型
      • 1.2.10 数组
      • 1.2.11 常量
    • 1.3 函数
      • 1.3.1 函数格式
      • 1.3.2 参数传递
    • 1.4 其他
      • 1.4.1 类型转换
      • 1.4.2 运算符
  • 第2章 汇编语言的语言元素
    • 2.1 程序结构
      • 2.1.1 整体结构
      • 2.1.2 分支
      • 2.1.3 循环
    • 2.2 数据
      • 2.2.1 数据存储
      • 2.2.2 整数
      • 2.2.3 浮点数
      • 2.2.4 数据格式
    • 2.3 函数
      • 2.3.1 参数传递的规则
      • 2.3.1 调用语句
      • 2.3.2 返回值传递
  • 第3章 C语言的汇编实现
    • 3.1 数据类型的实现
      • 3.1.1 整型
      • 3.1.2 浮点型
      • 3.1.3 指针与数组、结构
      • 3.1.4 类型转换
      • 3.1.5 寻址问题
    • 3.2 代码结构的实现
      • 3.2.1 循序
      • 3.2.2 分支
      • 3.2.3 循环
      • 3.2.4 优化对循环的影响
    • 3.3 函数的实现
  • 第4章 C与汇编的优缺点分析
    • 4.1 C语言
      • 4.1.1 优势
      • 4.1.2 劣势
      • 4.1.3 应用场景
    • 4.2 汇编语言
      • 4.2.1 优势
      • 4.2.2 劣势
      • 4.2.3 应用场景
    • 4.3 总结
  • 参考文献

第1章 C语言的语言元素

1.1 程序结构

1.1.1 循序结构

C语言中程序的执行方式为顺序执行,即当某一代码块中没有可能会导致跳转的语句,例如循环、分支、函数等时,程序从这一代码块的第一条指令,逐条执行到最后一条指令。例如在某一.c文件中写入下列代码:

int main()
{
A = 1;
B = 2;
A = A+ B;
return 0;
}

在这一程序中,main函数所运行的代码为完全的循序结构。

1.1.2 分支

(1)if语句

在C语言中,if语句的格式为:

if(条件表达式1)
{代码1}
else if(条件表达式2)
{代码2}
。。。
else
{代码n}

其中else if 和else不是必须的。
在每个if和else if后的括号中,为运行其后代码所需要的条件,当所有条件表达式均为否时,则执行else后面的代码,若没有else,则不做任何操作。
在C语言中,当条件表达式的值为0时,则认为条件表达式为否,而当条件表达式为任意非0值时,则认为条件表达式为真。除此之外,C++中还定义了bool类型,true表示真,false表示否,且true可视作为1,false可视作0。

(2)switch语句

在C语言中,switch语句的格式为:

switch(变量a)
{
case 情况1:
代码1
case 情况2:
代码2
。。。
default:
代码n
}

其中,变量为某一基本类型变量a,即char、int、float、double等类型变量。并且default标记不是必要的。
case后面的情况为a所可能的各种取值,当a满足其取值为某种情况时,程序将跳转到这一情况对应case后面的代码,并且程序将运行后面全部代码,包括其他case的代码,直到遇到break语句或运行完其后续的所有代码,才会退出switch代码块。
当a没有满足任何列出的情况时,程序将跳转到default后面的语句,运行情况同case。另外,当没有default时,程序将直接退出switch代码块。

1.1.3 循环

(1)while语句

在C语言中,while语句的格式如下:

while(条件表达式)
{
代码
}

当条件表达式为真时,程序将执行代码块中的代码。

(2)do-while语句

在C语言中,do-while语句的格式如下:

do
{
代码
}while(条件表达式);

程序将先执行一遍代码块中的代码,然后判断条件表达式是否为真,若为真,则重复执行代码块中的代码。

(3)for语句

在C语言中,for循环的语句格式如下:

for(初始化表达式; 条件表达式; 迭代表达式)
{
代码
}

程序先根据初始化表达式进行初始化,并判断条件表达式是否为真,若为真则运行代码块中的代码,随后运行迭代表达式,再重新判断条件表达式是否为真,当条件表达式不为真时,退出循环,否则继续运行代码块。

1.2 变量

1.2.1 全局变量

在C语言中,定义在函数外的变量为全局变量,其作用域为文件中的全体函数,以及调用该文件的其他文件中的全体函数,当其用static进行修饰时,其作用域仅为当前文件中的全部函数。当用extern进行修饰时,说明该全局变量需要在其他C文件中定义。
任何数据类型的变量均可被定义为全局变量,且全局变量的生命周期为整个程序的生命周期,当程序结束时,其内存才被释放,在此之前,其内存一直存在。
当代码中未对全局变量进行初始化时,其将被初始化为0。

1.2.2 局部变量

在C语言中,局部变量定义在函数内部或函数内部某一代码块中,其作用域为函数内部或代码块内部,以及函数内部的代码块中。
局部变量的生命周期为整个函数或代码块,当局部变量没有被显示初始化时,其值为一伪随机数,所以局部变量需要在使用前进行初始化。
但当局部变量用static进行修饰时,其值不会因为其所在函数结束而被修改,而是会在再次执行到这一函数时存储其先前的值。另外静态局部变量若没有进行显示初始化,其初始值为0。

1.2.3 整型变量

在C语言中,整型变量包括char、short、int、long、long long几种类型,其区别在于所需存储空间的大小以及其所能表示数字的大小,long类型在不同系统中往往有着不同的容量。
在C语言中,整形数据均采用补码存储,除char类型外,其他类型均默认为有符号整数,可以通过在int等关键字前添加unsigned关键字来声明无符号的数据类型,同理可以通过在char关键字前添加signed关键字特别声明有符号的以表示单字节变量。
另外,char类型大部分时候均用来表示ASCII码,并不经常用来表示数字。

1.2.4 浮点变量

在C语言中,浮点变量包括float、double、long double几种类型,其区别同样是其存储空间大小及其表数范围、表数精度。其中long double在不同系统中往往有不同的容量。
C语言中,浮点类型通过IEEE754编码实现。浮点变量通常用于表示小数以及过大的数,但其对于大部分数字而言只能存储近似值,不能保证绝对精确,而精确程度与其存储空间有关。

1.2.5 寄存器变量

在C语言中,需要频繁进行使用的变量可以通过register关键字将其定义为寄存器变量,寄存器变量总是保存在CPU的寄存器中,可以有最高的运行效率。
32位下由于寄存器空间的限制,寄存器变量只能为char、short、int类型。而且由于寄存器变量不存储在内存中,所以不可以对其用取地址运算符。

1.2.6 指针变量

在C语言中,指针变量存储一个虚拟内存的地址,任何数据类型均有与其对应的指针类型,除此之外,还有指向函数的函数指针。指针类型在32位编译模式下大小为4字节,在64为编译模式先大小为8字节。
可以通过取地址运算符&获取某一变量的地址,也可以通过*运算符访问一个指针所指向的内存空间。除此之外,当指针为结构体指针时,可以通过->运算符读取该指针所指向结构体的成员变量。

1.2.7 结构体

在C语言中,结构体是一种抽象数据类型,它一般表示某种需要通过多种数据类型才能精准表示的事物。一般定义结构体数据类型的代码格式如下:

struct 类型名
{
数据类型1 变量A;
。。。
数据类型n 变量N;
};

在定义结构体类型后,需要通过以下格式声明一个结构体类型变量:
struct 结构体类型名 变量名;
若想访问或修改结构体变量的某个具体属性,需要通过圆点运算符.进行读取。

1.2.8 共用体

C语言中,可以通过共用体对同一段内存进行不同种数据类型的存储、读取。其定义格式如下:

union 共用体类型名
{
可能的数据类型 变量;
};

在定义共用体类型后,需要通过以下格式声明一个共用体类型变量:
union 共用体类型名 变量名;
并通过原点操作符.来选择其存储哪种数据类型并进行读写。
共用体所占空间大小与其内部最大空间的数据类型相同,当用较小的数据类型模式读取存储较大数据类型的共用体时,只会读取内存的前几个字节,且具体读出的结果与大小端模式有关。

1.2.9 枚举类型

C语言中,对于某一概念中包括多种常量时,可以使用枚举类型,其定义格式为:

enum 枚举类型名
{
常量名1;
。。。
常量名n;
};

枚举类型的常量可以直接对其常量名进行访问,另外在枚举类型定义时可对其各个常量进行赋值,用以使其用来表示某个整数值。若没有在枚举类定义中设置常量值,则程序将从第一个常量从0开始顺序赋值;若只对第一个常量进行赋值,程序将从这个值开始为后续常量顺序赋值。
除此之外,枚举常量作为常量,不能被程序进行修改,只能将其赋值给某一变量。

1.2.10 数组

C语言中,所有类型的变量都可以在其声明时在其变量名后添加[]使其被定义为数组变量,通常[]中应以常量注明数组大小,但当数组被初始化时,数组大小可不进行注明。另外,变量名后可添加多个[]表示多维数组,但只有第一个[]可以不注明数组大小。
除此之外,char数组足够大时可被直接用来存储字符串,字符串可被视作以\0字符结尾的字符数组。

1.2.11 常量

C语言中,常量包括数字、字符串、宏常量、const常量几种
数字包括整数与小数,其中整数可以通过在数字前标注0前缀表示8进制,标注0x前缀表示16进制,另外可以通过在数字后加U后缀表示该数字为无符号数字,后缀L表示数字为long类型,否则将其视为十进制有符号int类型。而浮点类型可以通过在数字后加F表示该数字为float类型,否则为double类型。
在代码中所有显示出现的字符串均被视为常量,不可以对其进行修改,否则会报错。
C语言中可以通过预处理命令定义宏常量,会在预处理阶段将宏常量转化为其对应的常量,其定义格式为:
#define 常量名 常量数值
C语言中,还可以通过const关键字使某一变量值除定义时的初始化外,无法进行修改,即使其成为常量,但该常量的作用域仍与其定义位置有关。
另外,当一个指针变量被const关键字修饰时,其所指向的内存也不可通过这个指针变量进行修改。

1.3 函数

1.3.1 函数格式

在C语言中,函数表示某一段执行特定功能的代码,可以由其他函数进行调用,其定义格式为:

返回值类型 函数名(数据类型1 变量A, 。。。, 数据类型n 变量N)
{
代码
return 返回值;
}

当返回值类型为void时,return语句可以不写,也可以仅为return;用以标志程序退出位置。
通常情况,函数应该在编写实现代码前进行声明,函数声明格式如下:
返回值类型 函数名(数据类型1 变量A, 。。。, 数据类型n 变量N);
当在函数声明前添加static关键字时,可以将函数的使用范围限定在当前文件中。当在函数声明前添加extern关键字时,函数被视作需要从其他C文件中引用,因而不需要函数体。
另外,在每一个可以被编译为可执行程序的C文件中均有一个程序开始执行的入口函数,对于命令行程序而言,其入口函数为main函数,程序总是从main函数的第一行开始执行,且main函数无论通过直接或间接方法都无法访问到的函数通常可以被认为永远无法被执行。

1.3.2 参数传递

在调用某一函数时,开发者需要按照函数声明向函数输入约定参数进行调用,并接收其返回值。
函数在被调用时所接受的参数存储在其自身的局部变量中,对传递进来的参数进行修改,不会影响到传递的参数的值,但对于指针参数而言,其所指向的位置依然相同,因此对指针所指向的空间操作会影响到其他函数的运行,所以当有不想被函数修改指向空间的指针变量时,可以在其声明中加以const关键字修饰。
当函数的参数为数组或结构体时,可以视为传递的为数组或结构体的指针,对他们进行操作时也会导致其内部结构发生改变。
在C++中,可以在声明中通过引用变量直接对传递进来的参数进行操作,修改其数值。
另外,通常函数在运行时,只能直接访问其内部的局部变量以及各全局变量,不能访问到其他函数的局部变量。

1.4 其他

1.4.1 类型转换

在C语言中,类型转换分为隐式转换与显示转换。
隐式类型转换发生在小数据类型与大数据类型进行运算时,以及整数数据类型与浮点数据类型运算的时候。当某一小数据类型与大数据类型进行运算时,小数据类型会被转换为大数据类型再进行运算,并且计算结果为大数据类型。当整数数据类型与浮点数据类型进行计算时,整数数据类型会被转换为浮点类型再进行计算。另外在用整数数据类型变量接收浮点类型时,会自动将浮点类型转换为舍入到整数位的整数类型,再进行赋值;当用大数据类型接受小数据类型时,会自动将小数据类型转化为大数据类型,再进行赋值。
显示转换也叫强制类型转换,一般发生在大数据类型转换为小数据类型、将某一数字常量强制视为另一种类型的常量进行运算、强制将某一变量通过另一变量的格式进行解读时。需要注意的是,第一种情况下,强制类型转换可能会导致数据溢出和舍入。除此之外,有符号整数与无符号整数之间的强制类型转换,满足数据的位模式不变。

1.4.2 运算符

C语言中有大量运算符,他们在计算过程中会按照优先级进行运算,下列是运算符优先级及功能表
| | |

优先级 运算符 名称或含义 使用形式 结合方向 说明
1 [] 数组下标 数组名[常量表达式] 左到右
1 () 圆括号 (表达式)/函数名(形参表) 左到右
1 . 成员选择(对象) 对象.成员名 左到右
1 -> 成员选择(指针) 对象指针->成员名 左到右
2 - 负号运算符 -表达式 右到左 单目运算符
2 ~ 按位取反运算符 ~表达式 右到左 单目运算符
2 ++ 自增运算符 ++变量名/变量名++ 右到左 单目运算符
2 自减运算符 –变量名/变量名– 右到左 单目运算符
2 * 取值运算符 *指针变量 右到左 单目运算符
2 & 取地址运算符 &变量名 右到左 单目运算符
2 ! 逻辑非运算符 !表达式 右到左 单目运算符
2 (类型) 强制类型转换 (数据类型)表达式 右到左
2 sizeof 长度运算符 sizeof(表达式) 右到左
3 / 表达式/表达式 左到右 双目运算符
3 * 表达式*表达式 左到右 双目运算符
3 % 余数(取模) 整型表达式%整型表达式 左到右 双目运算符
4 + 表达式+表达式 左到右 双目运算符
4 - 表达式-表达式 左到右 双目运算符
5 << 左移 变量<<表达式 左到右 双目运算符
5 >> 右移 变量>>表达式 左到右 双目运算符
6 > 大于 表达式>表达式 左到右 双目运算符
6 >= 大于等于 表达式>=表达式 左到右 双目运算符
6 < 小于 表达式<表达式 左到右 双目运算符
6 <= 小于等于 表达式<=表达式 左到右 双目运算符
7 == 等于 表达式==表达式 左到右 双目运算符
7 != 不等于 表达式!= 表达式 左到右 双目运算符
8 & 按位与 表达式&表达式 左到右 双目运算符
9 ^ 按位异或 表达式^表达式 左到右 双目运算符
10 | 按位或 表达式 表达式 左到右
11 && 逻辑与 表达式&&表达式 左到右 双目运算符
12 || 逻辑或 表达式||表达式 左到右 双目运算符
13 ?: 条件运算符 表达式1?表达式2: 表达式3 右到左 三目运算符
14 = 赋值运算符 变量=表达式 右到左
14 /= 除后赋值 变量/=表达式 右到左
14 *= 乘后赋值 变量*=表达式 右到左
14 %= 取模后赋值 变量%=表达式 右到左
14 += 加后赋值 变量+=表达式 右到左
14 -= 减后赋值 变量-=表达式 右到左
14 <<= 左移后赋值 变量<<=表达式 右到左
14 >>= 右移后赋值 变量>>=表达式 右到左
14 &= 按位与后赋值 变量&=表达式 右到左
14 ^= 按位异或后赋值 变量^=表达式 右到左
14 |= 按位或后赋值 变量|=表达式 右到左
15 逗号运算符 表达式,表达式,… 左到右

第2章 汇编语言的语言元素

2.1 程序结构

2.1.1 整体结构

注意,下面所说的汇编语言均以AT&T为例。
汇编程序中每个节由.section进行标识,每个汇编程序通常有.data、.text、.bss三个节,其功能如下
.data节存储已初始化的变量,且其可读也可写,可以在代码中对其进行读写。
.bss节存储未初始化的变量,当使用这些变量时,可以视作他们都被初始化为0。
.data 节与.bss节共同构成了程序的数据段,用来存储汇编程序中的各种变量。
.text节存储程序的指令代码,该节中的代码为只读模式,不可进行修改。每一个可执行汇编程序中都应该至少有一个.text节。
通常在.text节的第一行代码中定义.global _start符号或.global _main符号,并在后面编写其程序体,以此作为程序的入口函数,并按照该函数中的代码顺序运行。
.text节也被视作汇编程序的代码段,用来存储程序中的各个函数的指令代码。
汇编程序中的栈段并不会在代码中显示定义,但代码段中有各种用于对栈进行操作的指令,例如push,pop等,另外程序可以通过对栈顶指针相对寻址的方式来间接使用栈段。栈段的数据为可读写模式。

2.1.2 分支

在汇编语言中,分支通过jmp等指令修改%rip寄存器实现,其中,jmp为无条件强制跳转,而其他跳转指令则是根据当前各条件位的状态判断是否跳转到其他指令地址。例如je指令在判断ZF条件码为1时才会跳转到其参数的地址。
在跳转指令中,可以通过*运算符来进行间接跳转,例如跳转到某寄存器所存内存地址等情况。
需要注意的是,汇编语言中的跳转指令通常可以跳转到任何位置,操作不当可能会造成严重错误,所以通常应该只用于函数内部的跳转。

2.1.3 循环

在汇编语言中,并没有直接提供用来构造循环的语法,但是可以通过设置循环起点以及循环终止条件,当终止条件不满足时,使用je等指令跳转到循环起点,从而实现循环的功能。通常,循环起点被标注为loop符号。

2.2 数据

2.2.1 数据存储

在汇编语言中,数据分为立即数、寄存器数据、内存数据三种。
常数通常直接使用立即数进行表示,在任何操作中,立即数均不能作为指令的接收部分。在AT&T中,立即数的表示方法为在数字前加$符号。
另外,可以在数字前加0x前缀以表示十六进制数字。
变量通常使用寄存器与内存进行存储,对于使用频率极高的变量,可以将其存储于寄存器中,以提高读取速度,而通常的变量,均存放在内存中。许多指令都只支持寄存器作为参数,所以许多时候,变量需要先读取至存储器中,才能进行操作,这种情况,存储器相当于一个实现功能所需的缓存。
另外,由于存储器数量比较少,所以大部分的数据通常都存放在内存中,对于作用域比较小的变量,通常通过%rsp和%rbp两个寄存器,将其存放在栈中,并使用他们对其进行读写。而对于全局性变量来说,通常使用绝对取址或通过%rip寄存器运算获取其位置,并进行读写操作。

2.2.2 整数

在汇编语言中,整数通过补码进行表示,且不区分有符号整数与无符号整数,同一位模式的数据既可以作为有符号整数操作,也可以作为无符号整数操作。但汇编语言中有各种针对相同位模式,分有符号和无符号两种对数据进行操作的指令。
另外,整数数据也按照大小分为单字节、单字、双字、四字四种类型,分别对应一个字节、两个字节、四个字节、八个字节四种情况,每种类型对应的指令分别有b、w、l、q四种后缀,以针对这四种类型的数据进行操作。需要注意的是,当寄存器中大数据强行作为小数据使用时,其高位数据通常会被保留,但四字数据强行转换为双字数据时,其高四位将被清零。

2.2.3 浮点数

在汇编语言中,浮点数也通过IEEE754编码进行表示,并且浮点数的存储方式与正常数据相同,但读取一个浮点数不应使用%rax等整数寄存器,而是应该使用%xmm、%ymm系列寄存器,并使用浮点数专用的指令进行浮点运算。

2.2.4 数据格式

在汇编语言中,指令的操作数按照下图的格式进行输入:

2.3 函数

2.3.1 参数传递的规则

在32位汇编程序中,参数的传递一般是按照将函数的参数从左到右依次压入栈中,然后通过函数的栈指针对各参数进行读取与修改。
在64位汇编程序中,参数的传递一般结合了寄存器实现,参数会依次存入%rdi、%rsi、%rdx、%rcx、%r8、%r9中,当参数多于6个时,其余参数会被压入栈中,并通过栈指针进行读取与修改。

2.3.1 调用语句

在汇编语言函数开始调用时,call指令会先将%rip中的数据进行压栈,然后将%rip修改为该函数的第一条语句,前几条语句通常是用来构建函数栈的,并按照顺序逐条执行函数中的语句,同时,当需要使用局部变量时,函数会将其存放在。最终,函数通常将运行leave和ret指令,从而将控制权转移回调用它的函数,并将栈清空。

2.3.2 返回值传递

通常,当函数有返回值时,该函数会在结束运行前将计算得到的返回值保存到%rax寄存器中,并在调用者函数中通过访问%rax寄存器进行对函数返回值的接受。

第3章 C语言的汇编实现

3.1 数据类型的实现

3.1.1 整型

在C语言被编译为汇编语言的过程中,表示整型数据的全局变量被翻译为汇编语言中的各种全局符号,表示整型数据的局部变量被转换为对应函数栈帧中的特定位置内存,而C源代码中出现的各整数常量则被翻译为立即数。具体实现过程描述如下:
对于全局变量,汇编代码中会将其翻译为全局符号,对于有初始值的全局变量,其在汇编代码中会被标志为.data节,并在符号内部填充相应初始值;而对于无初始值的全局变量而言,其会被标志为.bss节。但其具体使用相似,都是被编译为在汇编指令中直接使用相应全局符号。
而对于局部变量而言,汇编代码中,会将C语言各函数中的局部变量名无视,直接将局部变量存储在函数的栈帧中,并使用寄存器进行寻址来读写局部变量。需要注意的是,静态局部变量会被编译器解释为符号,并按照与全局变量相同的方式进行存储于使用,但其使用范围会被限制在相应的函数中。
另外,代码中出现的整型常量通常被直接翻译为汇编代码中的立即数,作为汇编指令的源操作数。

3.1.2 浮点型

对于浮点数,C语言在编译后会将表示浮点数的IEEE754编码按照十六进制数字整数进行存储、读取,但浮点数运算部分,代码会被编译为使用%xmm等寄存器并使用各种浮点数运算指令进行相关的浮点运算。

3.1.3 指针与数组、结构

当C语言被编译为汇编时,C语言中的指针会被视为整型变量,唯一区别是,C语言中针对指针的各种操作会被编译为一套特定的汇编代码,而通常的整型变量不可以使用这些操作,从而不可能出现非指针变量被编译后出现这些指令序列的情况。
数组通过在内存中对同一数据类型连续存储实现,访问数组某一元素,只需根据首字节地址以及其与首字节的偏移量即可实现。
结构和数组的结构相似,但其内部的数据类型不同,占用空间也不同,每种数据类型的存储位置按照其在结构体中定义的顺序,而且每一个数据其距离结构体首字节的距离满足其自身大小的整数倍,从而实现对齐。
另外,在数组、结构等数据类型被编译为汇编语言时,其内部数据通常存储在栈中,而用表示其首字节地址的整型变量作为其位置的标识,并根据其内部结构进行相对寻址从而读写他们内部的数据。

3.1.4 类型转换

C语言中的整数间类型转换在汇编语言中通过movzbl等指令以及CPU寄存器特性等功能实现。对于自动类型转换而言,相应计算代码会被编译为先使用类型转换命令将小数据转换为大数据类型随后将两个相同数据类型的数据再进行运算。而强制类型转换也是通过这些指令来进行的,区别仅在于C语言中的限制。
对于与浮点数有关的类型转换,均是通过浮点数指令来进行的,例如通过vcvtsi2ss指令将整数转换为单精度浮点数。

3.1.5 寻址问题

在C语言被编译为汇编语言的过程中,全局变量的寻址通常使用与%rip共同使用来间接访问某一绝对地址的方式实现,而局部变量的寻址则是通过在其所在函数的栈帧中,通过%rsp进行相对寻址来实现。

3.2 代码结构的实现

3.2.1 循序

当C语言被编译为汇编语言时,C语言中的循序代码会被逐条翻译为相应的汇编代码,其运行顺序不变,从而实现了C源代码编译后的汇编代码依然循序。

3.2.2 分支

当C语言被编译为汇编语言时,if语句会被翻译为先进行条件判断,当满足条件时跳转到相应代码,不满足条件时顺序执行的汇编结构。
而switch语句将会构建一个跳转表,对于每一种情况,都将其代码进行标记,并将标记存放在跳转表中,当满足条件时,控制将会跳转到相应代码并顺序执行。

3.2.3 循环

C语言中包含三种循环,其被翻译为汇编代码时有不同的表现,细节如下:
对于do-while循环,其汇编代码实现方式为在代码第一行前通过一个符号进行标志,当运行结束这段代码时,判断循环条件,若满足条件,则跳转到前面的符号,从而实现循环功能。
对于while循环,程序会在代码部分和条件判定部分分别进行标记,在开始循环前,程序会跳转到条件判定部分,当条件满足时,再跳转到代码部分并循序执行相应代码,执行结束时,控制会回到条件判定部分,从而实现循环功能。
对于for循环,程序会在循环条件判定部分及循环体部分进行标记。在循环代码执行前执行循环初始化代码,随后将控制转移到条件判定标志,当条件满足时,执行循环体,紧接着执行循环迭代代码,再进行条件判定,从而实现for循环功能。

3.2.4 优化对循环的影响

当编译器开启了优化选项时,编译器可以对循环生成的代码进行优化,从而实现效率的提升,其具体实现可以分成以下几点:
首先,当某一循环体为空时,优化会导致程序忽略该循环。其次,当循环中的运算结果若在后续代码中未被使用,循环将直接被优化删除。除此之外,优化可能为了提高程序效率调整部分代码次序或删除部分代码。另外,当代码中重复计算相同的数据时,该部分代码会被编译器移出循环。
需要注意的是,用于实现等待一段时间的循环通常会被编译器优化。

3.3 函数的实现

C语言被编译为汇编语言时,函数名会被翻译为一个.text节符号,并在它的内部定义函数的具体实现代码。每个函数的前几行指令,通常为开辟新的函数栈帧以保存其内部局部变量等信息,并在最后几行调用leave、ret等指令,将控制转移回其调用函数。
对于函数各个参数的传入,以64位系统为例,调用函数需要在调用所需函数前将函数所需要的各个参数存放在相应寄存器中,当参数数目过多,寄存器无法容纳时,函数栈帧将会有一块专门的参数构造区,用以存储参数7之后的参数,并通过%rsp、%rbp进行相对寻址从而对其进行读写操作。需要注意的是,若在C语言代码中对变量进行取地址运算,则该变量通常会被保存在栈中。
当函数代码运行完毕前,需要将要返回的数据保存在%rax中,并在调用函数中接收并进行相应的操作。

第4章 C与汇编的优缺点分析

4.1 C语言

4.1.1 优势

C语言作为一种高级编程语言,其抽象程度比汇编语言更强,也更加贴近于人的理解过程。它的优点包括以下几个方面:
C语言,将对内存中各个地址的数据的操作抽象为对一个个变量的操作,只需要专注于某个算法的具体实现流程,而无需过多考虑底层硬件,这一特点使得它相比于汇编语言能让人更好的了解与设计某段程序的功能以及其运行过程,使得其编程思路相比于汇编语言也更加人性化。所以,C语言的开发速度相比汇编语言大大提高,同时其也大大降低了编程的门槛与难度。
除此之外,若一个C程序不调用任何与某个特定平台相关的库以及其中的函数,则将它的源代码复制到不同的平台,并使用该平台上的编译器,则其编译后的程序的运行效果将完全相同,而不受平台的影响,其可移植性相比汇编语言更强。
另外,C语言也是程序员入门的较好的编程语言,他的内容比较少,而且与底层硬件紧密相关,能够帮助学习者更快地了解计算机的相关知识。

4.1.2 劣势

C语言作为一个高级编程语言也有着不少缺点,当CPU出现某些新特性时,要想利用他们,C语言的编译器可能不得不进行改变,甚至对于某些特定的特性,C语言很难将其进行利用,进而导致程序对CPU无法进行充分地利用。
另外,C语言虽然是一种比较高效的高级语言,但对于一个熟练的汇编语言程序员来说,汇编代码的效率往往比C语言更高。

4.1.3 应用场景

当某个学生希望学习编程相关知识时,应该首先考虑内容较少但却与底层硬件紧密相关的C语言作为入门编程语言。
当编写操作系统时,对于一些抽象程度较高的功能,应该使用C语言进行编写,从而简化编写过程,将注意力更多地放在程序的功能上,并减少潜在的BUG。
当编写一些功能众多、体量巨大又追求较好的效率,但与计算机底层关系并不是很大却也有部分相关的项目时,也应该采用C语言以提高开发效率。
当编写一些嵌入式设备的程序时,但开发者对该设备CPU所使用的汇编语言不了解时,也应采用C语言,从而完成任务。
当编写的程序可能需要在多个平台发布时,相比于汇编语言也应该考虑C语言,从而简化移植过程。

4.2 汇编语言

4.2.1 优势

汇编语言作为一种抽象程度较低的,比较原始的编程语言,其优点在于好的汇编程序员可以使程序占用更少的空间、发挥更好的效率,以及它可以支持CPU的全部特性两方面。
相比与高级语言编译生成的汇编代码而言,由熟练的汇编程序员所编写的代码往往可以减少各种不必要的操作,更加充分地使用各种数据,并通过各种技巧来提高程序的效率,进而使相同功能的汇编代码有着更高的效率。
另外,汇编代码中可以应用无法直接在高级语言中使用的CPU的特性,从而使汇编代码在某些场合下相比于高级语言程序有着更好的功能性,极大提高特定工作的效率。

4.2.2 劣势

汇编语言也有自己的劣势,具体表现为以下几个方面:
汇编语言相比于C语言需要对计算机底层的知识有更加充分的掌握,否则很难编写出优质的汇编代码,另外,想要编写出比高级语言效率更高的代码,需要程序员大量的经验,上手难度较高。
汇编语言需要对每一步机器运行过程进行编写,需要消耗程序员大量精力,同时也使程序的开发效率极大降低。
汇编语言往往在不同架构的CPU上有不同的具体指令,将程序从一个平台向另一个平台需要进行大量工作,甚至完全重写代码,可移植性极差。

4.2.3 应用场景

当编写计算机操作系统时,其最基本的功能应提供汇编语言实现,从而提高效率。
当编写效率要求较高、功能性较强、存储空间较少的程序时,应使用汇编语言实现。
当编写高级语言的解释器、系统函数库等高级语言基本组件时,应用汇编语言实现,以提高效率。
当编写特殊环境下的嵌入式设备时,应使用汇编语言编写以最大可能避免各种问题。

4.3 总结

C语言与汇编语言的各种特性决定了不可能有某一方完全代替另一方。当要求开发速度快、可以移植性强、学习简单,但对运行效率没有极度追求,程序功能性也不需要太强,例如通常的软件编写等情况下,C语言无疑为更好的选择;而在要求运行效率高,程序功能性强,但对开发效率、可移植性没有太高要求时,例如航天设备软件编写等情况下,汇编语言无疑更加实用。
总的来说,C语言主要应用于抽象程度较高的领域,而汇编应用主要应用于抽象程度较低的领域。所以,无论是汇编语言还是C语言都有着其各自的用武之地,两种语言均需要培养相应的人才。

参考文献

[1]C语言运算符优先级(超详细):http://blog.csdn.net/huangblog/article/details/8271791
[2] ATT汇编语言格式:https://max.book118.com/html/2017/0906/132304926.shtm
[3] 第二部分ATT汇编语言:https://wenku.baidu.com/view/4bb1dad9777f5acfa1c7aa00b52acfc788eb9f6e.html
[4] 百度百科-汇编语言:https://baike.baidu.com/item/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/61826?fr=aladdin#5_1

本文为作者原创,部分内容表述可能并不准确,请读者仅将本文作为一份参考,若发现错误请酌情提醒本人改正
另附本人关于本次大作业所上交实验代码的下载网址

你可能感兴趣的:(程序人生,c语言,反汇编)