欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

[C语言]

时间:2023-06-01
[ C语言 ] ___ 程序

简介源文件编辑编译四部曲

Step:1,预处理阶段(Pre-processing)Step:2,编译阶段(Compiling)Step:3,汇编阶段(Assembling)Step:4,链接阶段(linking) 目标文件资料 简介


程序,实则为实现特定目标或解决特定问题而用(能被人读懂的)计算机语言编写的命令序列的集合。

程序一般分为系统程序和应用程序两大类,应该包括以下两方面的内容:

数据的描述;(数据结构)操作的描述。(算法)

作为一门典型的编译性语言,源文件是需要编译器驱动程序翻译成可执行目标程序才能运行。

源文件编辑

源文件是指纯文本,内部没有特殊格式的文件,但对于计算机语言来说,其源文件的命名都会有一个特定的后缀,以方便编译器识别,程序员区分,例如:

计算机语言(编程语言)后缀名C.cC++.cppPython.pyJava.java……

选用几款好用的文本编辑器,可以让编码更方便快捷有效率

VimSublime TextUltraEditGNU EmacsVisual Studio Code

使用文本编辑器进行代码编辑时需要注意以下几个问题

文件编码需注意;严格区分中英文;严格区分全半角。 编译四部曲


源文件中的代码是由固定的词汇按照固定的格式组织起来的,简单直观,程序员容易识别和理解。
但是,对于Cpu而言,识别由一系列计算机指令和数据的集合组成的程序,是二进制形式的,两者不相通的。
这时候就需要一个特殊软件工具,读取源文件的代码词汇、句子以及各种特定的格式并将它翻译成一个Cpu能够识别的可执行目标程序,这个特殊的软件工具,叫编译器驱动程序,而这个过程被称为编译(翻译)。

在C语言中编译器是有很多种的,不同的平台下也有不同的编译器,例如:

(Windows,MinGW)(Linux,Gcc)(macOS,LLVM/Clang)

以Gcc的编译过程为例子,Gcc命令的一般格式为:

gcc [选项] [源文件] [选项] [目标文件]

其中,选项和目标文件可缺省,Gcc默认生成可执行文件,命名为:a.out

参数说明-E使Gcc编译器预处理完毕即刻停止-S使Gcc编译器在执行完编译后停止,生成汇编程序-c使Gcc编译器在执行完汇编后停止,生成目标文件-o使用此选项时,可指定生成可执行文件名,如没有添加其他选项,则Gcc一步到位执行到链接后停止,生成最终的可执行文件(缺省选项时,默认可执行文件名为a.out)

我们可以使用Gcc一步到位编译:

localhost@linux:~$ gcc main.c -o main

使用Gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤(预处理器,编译器,汇编器,连接器)一起构成了编译系统

Step:1,预处理阶段(Pre-processing)

预处理,又称为预编译,即生成预编译文件(预编译,.i)。预处理器读出源代码,对其中内嵌的指示字进行响应(可以说预处理的行为是由指示字/预处理指令控制的),产生源代码的修改版本。修改后的版本会被编译器读入,使用编译器会自动运行预处理器,对程序进行编译预处理。
源代码中的预处理指令叫做指示字(directive) ,从源代码中可以轻易发现,它们以井号#开始,在每行都是第一个非空字符,而井号通常都在第一列,后面紧跟着指示字的关键字。其主要处理规则如下:

处理所有#include指令 ,将被包含的文件插入到该预处理指令的位置;(这个过程是递归的,也就是说被包含的文件还可能包含其他文件)将所有的#define指令删除,并展开所有的宏定义,实现文本替换;处理所有条件预处理指令,比如#if,#ifdef,#elif,#else,#endif;保留所有#pragma指令,因为编译器需要使用它们;(设定编译器的状态或者是指示编译器完成一些特定的动作)添加行号和文件标识;(编译时检查到错误或警告的行号提示与调试时查找性能瓶颈和跟踪软件bug的所用的行号提示都是出自这里)所有的注释删除。(//和)

使用Gcc编译器预处理操作的命令如下:

localhost@linux:~$ gcc -E main.c -o main.i

Step:2,编译阶段(Compiling)

编译,指的是使用编译器将源程序翻译成为目标程序,即转换预编译文件为汇编文件(翻译,.S),但不是指程序从源程序到可执行程序的全部过程。
编译器所要完成的工作是一个复杂的整体的过程,整个工作过程是划分成步骤进行的,每个步骤将源程序的一种表示形式转换成另外一种表示形式,各个阶段进行的操作在逻辑上是紧密连接在一起的,符号表管理器和出错处理贯穿编译器工作的各个步骤,典型的划分方法主要分为以下几个步骤:

使用Gcc编译器编译操作如下:

localhost@linux:~$ gcc -S main.c -o main.S

Step:3,汇编阶段(Assembling)

汇编,指的是使用汇编器将源程序翻译成为目标程序,即翻译汇编文件为二进制文件(翻译,.o)。
使用Gcc编译器汇编操作如下:

localhost@linux:~$ gcc -c main.S -o main.o

此期间进行了汇编指令的翻译,符号表,重定位表的生成,没有生成最终目标可执行程序的,而是生成的是可链接的二进制文件。

高级语言、汇编语言、机器语言是分别对应计算机系统的不同抽象层级。
早期,没有高级语言和汇编语言的时候,程序员靠查询机器语言手册,把想要计算机完成的工作的一系列指令翻译成机器语言并提供给计算机,这个过程是及其繁琐的。
后来,人们发明了助记符,把机器语言抽象成一系列直观的,比较容易记住的汇编指令,一条汇编指令可以完成诸如内存读写、算术逻辑运算、栈维护、过程调用和返回等等操作,并通过汇编器将汇编指令翻译成机器指令。
再后来,人们觉得汇编指令抽象程度还不够高,于是发明了高级语言。

编译器产生汇编而不是适当的机器代码的其他原因是:

汇编程序使用的符号地址而不是硬编码的机器地址,使代码重定位更加容易。链接代码可能涉及安全检查,例如类型检查,而使用符号名称则更容易。通过更改汇编器而不是代码生成器,可以更轻松地适应机器代码中的微小更改。 Step:4,链接阶段(linking)


链接,是一个打包的过程,指的是使用链接器(linker)将所有二进制形式的目标文件和系统组件组合成一个可执行文件,处理的是可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息(一般是虚拟内存地址),这个文件可被加载(复制)到内存并执行。
而链接器的主要作用是把各个模块之间相互引用(全局符号的互相引用,链接器往往会忽视局部符号)的部分处理好, 使得各个模块之间能够正确的衔接,把一些指令对其他符号地址的引用加以修正。
使用Gcc编译器链接操作如下:

localhost@linux:~$ gcc main.o -o main

在目标文件中,各个段没有具体的起始地址,只有段大小信息,各个标识符没有实际地址,只有段中的相对地址,段和标识符的实际地址需要链接器具体确定,把可重定位文件依次读入,分析各个文件的文件头,进而依次读入各个文件的段,并计算各个段的虚拟内存位置,把每一个符号定义与一个内存位置的符号引用相关联(符号解析),从而重定位这些段并合并,然后修改所有对这些符号的引用,使得他们指向这个内存位置(确定最终地址)。
在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的,链接器在链接时,会根据符号名称去相应模块中寻找对应符号。待符号确定之后,链接器会重写之前那些未确定的符号的地址,这个过程就是重定位。链接一般分为静态链接、载入时动态链接以及运行时动态链接三种。

目标文件

对于整个编译系统而言,处理的每一个源程序,都将最终都会得到相应的目标文件。目标文件中所存放的是与源程序等效的目标的机器语言代码。目标文件的格式是ELF格式,在Linux平台中有着特有的文件格式,是一种对象文件的格式。我们可以使用readelf命令查看其构成,由段组成,通常一个目标文件中至少有两个段:

代码段

包含的主要是程序的指令。可读和可执行的,但不可写。 数据段

主要存放程序中要用到的各种全局变量或静态的数据;可读,可写,可执行。

ELF格式,常被称为可执行和可链接格式 (Executable and linkable Format,ELF),是可执行文件、目标代码、共享库和核心转储的通用标准文件格式。UNIX环境下主要有三种类型的目标文件:

可执行目标文件(Executable File,由于它已经全部完成了重定位工作,可以由操作系统进行加载到内存中执行的文件)可重定位目标文件(Relocatable File,用于和目标文件链接并生成可执行目标文件或者共享库的二进制代码和数据)共享目标文件(Shared Object File,一种特殊的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接)

那么,可能有的朋友就会想到既然目标文件和可执行文件的格式是一样的,为什么还要再链接一次呢?
因为编译只是将写的代码转换成了二进制形式,它还需要和系统组件(比如标准库、动态链接库等)结合起来,这些组件都是程序运行所必须的。目标文件经过链接(link)以后才能变成可执行文件。

编译器和汇编器生成可重定位文件(包括共享文件),连接器生成可执行文件。我们在最后是要运行生成的可执行文件的,如不是当前用户和所属组生成的,则是要赋予权限,才能执行

localhost@linux:~$ chmod a+x main

一般运行可执行文件有两种方式:

绝对路径(~:家目录,这里我用${HOME}代替,如不确定当前位置,则可以使用pwd查看)

localhost@linux:~$ ${HOME}/main

相对路径(以当前位置为准,这里我用.代替)

localhost@linux:~$ ./main

资料

ELF_Format
man_elf

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。