Skip to content

逐行解析

  1. #include <stdio.h>

在C语言中,通过井号#作为特定的标记,与代码实现部分(void mainprintf(...))明显不同。

在这里,#include所做的操作,就是将后面跟的库,原封不动的**“复制粘贴”**到此程序。

可以看到,我们导入了一个stdio.h文件即标准输入输出(STanDard I/O),由于使用了其标准输入输出库文件中的一个函数printf()作为展示结果到命令窗口。

这里的.h表明这是一个头文件(header),C语言允许你通过头文件的方式,将一些声明与具体实现分离,可在头文件中仅做声明,在.c文件中再具体实现。

  1. int main(){}

在C/C++中main(){}的形式是整个程序的入口(这表明它是每个C程序必须的函数),也是整个程序执行真正开始的地方。其前方的int是一个关键字,含义为整型数字,它指明了,最终执行完{}中所有的内容后,将要返回给操作系统什么样的值,操作系统默认期望值为0以标志正确执行成功。

从此可以看出{}是一个函数或其他结构的边界线,它编译时在语法分析中被单独切开作为标记,在后续的编译中两两配对;而()中填入该函数可以接受的输入参数。

  1. printf("Hello World!\n");

在这一行,我们使用了库中的一个与屏幕交互的预定义函数,其含义为将printf()括号内的内容按照要求输出到屏幕上。在这里可以看到,字符\n似乎没有在屏幕中显示,实际上,\n是对格式的一种要求的特定转义字符,它的作用是,当输出到此处是换行。

类似的还有如下表,这些转义序列在C/C++程序中被编译器特殊处理,不会按字面值输出,而是执行相应的控制功能或显示特殊字符

转义序列名称描述
\n换行符将光标移到下一行开头
\t水平制表符将光标移到下一个制表位置
\r回车符将光标移到当前行开头
\\反斜杠表示一个反斜杠字符
\"双引号表示一个双引号字符
\'单引号表示一个单引号字符
\0空字符(null)字符串结束标记
\a警报(alert)发出系统提示音
\b退格符将光标向左移动一个位置
\f换页符将光标移到下一页开头
\v垂直制表符在文本中垂直移动光标
\xhh十六进制值表示ASCII码为十六进制值hh的字符
\ooo八进制值表示ASCII码为八进制值ooo的字符
\?问号表示一个问号(在某些情况下避免三字符组)

可以轻易看出,\负责将某一种字符转化成另一种含义,即\为转义字符。这种方式在许多编程语言是通用的。

例如,在一些shell解释器中,想要输出某些特定字符,必须进行转义,因为这些字符在解释器中被定义为一些操作或对象:

bash
# 这二者的输出是不一样的
echo $0
echo \$0
  1. return 0;

return作为一个关键字,将其后面的内容作为结果返回到函数外,它可以返回多种类型的数据,包括int, float , double(整数、浮点数)等


程序执行的背后

解释器

当打开cmd、powershell窗口或使用类Unix终端时,背后实际运行了一个cmd.exe/pwsh.exe/shell解释程序。

它负责接受给定的输入命令及参数,根据内置或下载的二进制程序,查询命令的存在性(合法性),执行相应的二进制程序并响应输出。

这个过程看似是连续的、不间断的,但机器并不是在接收到你的命令后连续地执行的,机器可能还并发(在极短时间内、ms级切换)的执行图形界面渲染等程序。

因此,其能理解并执行输入的gcc main.c -o main命令

编译过程

gcc把预编译和编译合并为一个步骤,对于C使用cc1指令完成,C++则使用cc1plus,gcc是对后台程序的包装,其根据不同参数调用cc1、as、ld(预编译、汇编、链接)

预编译

主要处理所有以#开头的预编译指令,并生成行号和文件标识,删除所有注释、/**///

  • 删除所有的#define,并展开所有的宏定义
  • 处理所有条件编译指令,如#if#ifdef#endif#elif#else
  • 处理#include预编译指令,将文件替换到其位置,该过程是递归进行的
  • 保留所有的#progma编译器指令,编译器需要用到他们,例如#pragma once防止重复引用等等
  • 删除所有注释、/**///
  • 添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告时能够显示行号,快速排错。

C/C++的宏替换和文件包含工作交由单独的预处理器,而不归入编译器范围

  • C语言中源代码文件扩展名为.c,头文件扩展名为.h,经过预编译后生成.i文件
  • C++中源码文件扩展名为.cc.cpp.cxx等,头文件扩展名为.hpp等,经预编译后生成的则是.ii文件

编译

将预编译后产生的.i.ii文件进行一系列处理后,生成汇编代码

词法分析

利用类似于有限状态机的算法,将源码程序输入到扫描机中,将其中的字符序列划分成一系列标记(记号)

词法分析产生的标记分类包含:关键字、标识符、字面量(数字、字符串)、特殊符号(加号、等号等等)

语法分析

语法分析器对词法分析器产生的标记,进行语法分析,产生语法树,如下。

与此同时,运算符优先级就确定了下来,如果表达式出现不合法(括号不匹配、表达式缺少操作等),编译器就会报错并返回相应报错信息,即只完成对表达式语法层面分析。

语义分析

表达式是否有意义的判断,分析的是静态语义(编译时即可分析)。相应的动态语义需要在运行期才能确定。

静态语义通常包括:声明和类型的匹配,类型的转换。例如:不合法的类型转换或赋值就由语义分析器检出。

中间代码生成及优化

这里的优化是源代码级别的一个优化过程,源码优化器会将整个语法树转换为中间代码(语法树的顺序表示),十分接近目标代码,而优化就是将语法树的可优化的子树替换为一个优化后的节点。

中间代码具有多种类型,最常见的是**“三地址码”“P-代码”**

  • 三地址码:c = a op b,(op表示操作)

中间代码使得编译器可以被划分为前端和后端

  • 编译器前端负责产生与机器无关的中间代码
  • 编译器后端将中间代码转换为机器代码

源代码优化器产生中间代码,标志着其后的过程都属于编译器后端,编译器后端主要包括:代码生成器和目标代码优化器

目标代码生成

由代码生成器将中间代码转化为目标机器代码,生成一系列的代码序列(汇编语言表示)

目标代码优化

对上述的目标机器代码进行优化,寻找合适的寻址方式、使用移位代替乘法运算、删除多余指令等

汇编

汇编代码与机器指令是一一对应的,如下,通过as将汇编码一一翻译成机器码,产生.o.obj文件

但,变量和数组等类似二次寻址的操作的地址仍没有确定,因此需要链接器将其构建成一个完整程序。

链接

链接程序ld 将生成的汇编代码与引入的库文件合并,生成最终可执行程序。