当前位置:文档之家› C程序的编译链接过程

C程序的编译链接过程

C程序的编译链接过程
C程序的编译链接过程

C程序的编译链接过程

编译,编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。

C源程序头文件-->预编译处理(cpp)-->编译程序本身-->优化程序-->汇编程序-->链接程序-->可执行文件

1.编译预处理

读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理

[析] 伪指令主要包括以下四个方面

(1)宏定义指令,如#define Name TokenString,#undef等。对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的 Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。

(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉

(3)头文件包含指令,如#include "FileName"或者#include 等。在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。

包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(<>)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。

(4)特殊符号,预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。

2.编译阶段

经过预编译得到的输出文件中,将只有常量。如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\,等等。预编译程序所要作

得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

3.优化阶段

优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。上图中,我们将优化阶段放在编译程序的后面,这是一种比较笼统的表示。

对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。

后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。

经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。

4.汇编过程

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成。通常一个目标文件中至少有两个段:

代码段该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

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

UNIX环境下主要有三种类型的目标文件:

(1)可重定位文件其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

(2)共享的目标文件这种文件存放了适合于在两种上下文里链接的代码和数据。第一种事链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。

(3)可执行文件它包含了一个可以被操作系统创建一个进程来执行之的文件。

汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

5.链接程序

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:

(1)静态链接在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。

(2)动态链接在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

经过上述五个过程,C源程序就最终被转换成可执行文件了。缺省情况下这个可执行文件的名字被命名为a.out。

ARM裸机课程c语言之编译链接

c 语言之编译链接 1、C 语言为什么需要编译链接1.1、编译链接的流程(1)编译流程图 (2)编译链接实例 由源码到可执行程序的整个过程:以我们再熟悉不过的hello.c 为例 Hello.c #include int main(int argc,char *argv[]){ printf("hello world.\n");return 0;} 对于这个c 语言程序,我们必须经过一定手段,将其编程二进制可执行文件,然后由系统加载执行。在Linux 系统中,gcc 编译时,gcc 编译程序会读取源代码hello.c 文件,并且将其翻译成一个可执行文件hello,整个过程共四个阶段,由编译工具链完成,在这里我们来完整的看下对hello.c 进行编译链接的四个过程。 .c 源码 预处理器 预处理文 编译器 汇编文 汇编器目标文件.o 链接器 可执行程序 库文件 其他目标代

第一过程:预处理(cpp),在命令行下输入gcc-E hello.c-o hello.i(gcc 预处理)的命令,预处理器会对以#开头的预处理命令进行处理。譬如hello.c 中的#include,预处理器会将系统中的hello.h的具体内容读取到文本中,替换原有的#include。得到一个新的C程序,我们一般称为.i 文件,这里得到的hello.i文件格式如下: Hello.i ... ... extern void funlockfile(FILE*__stream)__attribute__((__nothrow__,__leaf__)); #943"/usr/include/stdio.h"34 #2"hello.c"2 int main(int argc,char*argv[]) { printf("hello world.\n"); return0; } 第二过程:编译(cc),在命令行下输入:gcc-S hello.i-o hello.s(gcc 编译),当然也可以gcc-S hello.c-o hello.s,只是这种方式是由预处理器和编译器一起完成的,编译器将hello.i翻译成了hello.s汇编文件,汇编程序是一条条通用的机器语言指令。 hello.s .file"hello.c" .section.rodata .LC0: .string"hello world." .text .globl main .type main,@function main: .LFB0: .cfi_startproc pushl%ebp .cfi_def_cfa_offset8 .cfi_offset5,-8 movl%esp,%ebp .cfi_def_cfa_register5 andl$-16,%esp subl$16,%esp movl$.LC0,(%esp) call puts movl$0,%eax leave .cfi_restore5

Fluent_UDF_第七章_UDF的编译与链接

第七章 UDF的编译与链接 编写好UDF件(详见第三章)后,接下来则准备编译(或链接)它。在7.2或7.3节中指导将用户编写好的UDF如何解释、编译成为共享目标库的UDF。 _ 第 7.1 节: 介绍 _ 第 7.2 节: 解释 UDF _ 第 7.3 节: 编译 UDF 7.1 介绍 解释的UDF和编译的UDF其源码产生途径及编译过程产生的结果代码是不同的。编译后的UDF由C语言系统的编译器编译成本地目标码。这一过程须在FLUENT运行前完成。在FLUENT运行时会执行存放于共享库里的目标码,这一过程称为“动态装载”。 另一方面,解释的UDF被编译成与体系结构无关的中间代码或伪码。这一代码调用时是在内部模拟器或解释器上运行。与体系结构无关的代码牺牲了程序性能,但其UDF可易于共享在不同的结构体系之间,即操作系统和FLUENT版本中。如果执行速度是所关心的,UDF文件可以不用修改直接在编译模式里运行。 为了区别这种不同,在FLUENT中解释UDF和编译UDF的控制面板其形式是不同的。解释UDF的控制面板里有个“Compile按钮”,当点击“Compile按钮”时会实时编译源码。编译UDF的控制面板里有个“Open按钮”,当点击“Open按钮” 时会“打开”或连接目标代码库运行FLUENT(此时在运行FLUENT之前需要编译好目标码)。 当FLUENT程序运行中链接一个已编译好的UDF库时,和该共享库相关的东西都被存放到case文件中。因此,只要读取case文件,这个库会自动地链接到FLUENT 处理过程。同样地,一个已经经过解释的UDF文件在运行时刻被编译,用户自定义的C函数的名称与内容将会被存放到用户的case文件中。只要读取这个case文件,这些函数会被自动编译。 注:已编译的UDF所用到的目标代码库必须适用于当前所使用的计算机体系结构、操作系统以及FLUENT软件的可执行版本。一旦用户的FLUENT升级、操作系统改变了或者运行在不同的类型的计算机,必须重新编译这些库。

编译Compile与链接Link

编译Compile与链接Link 要连接要先编译,如果没编译会连接时会自动编译。举个例子,如果你有两个文件,对一个文件进行编译只是讲这个文件编译成了目标文件.obj,但是另外一个文件没有,需对另外一个文件进行编译,可以观察结果输出框的显示,这时连接便会将两个文件连接在一起,才会将互相调用的函数等联系起来,生成.exe。如果不编译直接点连接,系统自动将两个文件编译,并连接,生成.exe文件。 假设两个文件分别是a.cpp,b.cpp,a调用b的函数: 编译a: --------------------Configuration: a - Win32 Debug-------------------- Compiling... a.cpp a.obj - 0 error(s), 0 warning(s) 然后编译b: --------------------Configuration: a - Win32 Debug-------------------- Compiling... b.cpp b.obj - 0 error(s), 0 warning(s) 然后连接: --------------------Configuration: a - Win32 Debug-------------------- Linking... a.exe - 0 error(s), 0 warning(s) 假如编译一个后直接连接: 编译a:无变化 --------------------Configuration: a - Win32 Debug-------------------- Compiling... a.cpp a.obj - 0 error(s), 0 warning(s) 直接连接: --------------------Configuration: a - Win32 Debug-------------------- Compiling... b.cpp Linking... a.exe - 0 error(s), 0 warning(s) 如果两个都不编译,直接点连接: --------------------Configuration: a - Win32 Debug-------------------- Compiling... a.cpp b.cpp Linking... a.exe - 0 error(s), 0 warning(s) 注意:假如文件没有被修改的话,编译一次后不会再进行编译了,可以直接连接,如果这时不编译,直接点连接,也只是连接,没有编译过程。 编译.h文件会提示不能编译,因为.h一般存放的是函数的声明,类的定义等,系统不会编译,但是可以在头文件中点连接进行整个程序的编译连接过程。

C、C++程序编译过程

C/C++程序编译过程 学习各种外挂制作技术,马上去百度搜索"魔鬼作坊"点击第一个站进入、快速成为做挂达人。 当我们进行编译的时候,要使用一系列的工具,我们称之为工具链。 其中包括: 预处理器CPP 编译器gcc/g++ 汇编器as 连接器ld 一个C/C++程序编译过程包括下面几个阶段: 1.预处理预处理器cpp将对源文件中的宏进行展开。 2.编译gcc将c文件编译成汇编文件。 3.汇编汇编器as将汇编文件编译成机器码。 4.连接链接器ld将目标文件和外部符号进行连接,得到一个可执行二进制文件。 下面以一个很简单的hello.c来探讨这个过程。 #include #define BUFSIZE1024 int main(int argc,char*argv[]) { char hello[BUFSIZE]="Hello my friend!"; printf("%s\n",hello); return0; }

1.预处理(预处理器cpp): gcc会首先调用CPP进行预处理: [butbueatiful@xt myhello]$cpp hello.c>hello.i 或 [butbueatiful@xt myhello]$gcc-E hello.c>hello.i 我们用vi hello.i查看hello.i的内容如下: …… int main(int argc,char*argv[]) { char hello[1024]="Hello my friend!"; printf("%s\n",hello); return0; } 我们可以看到,文件中宏定义BUFSIZE出现的位置被1024替换掉了,其它的内容保持不变。 2.gcc将c文件编译成汇编文件(编译器gcc): 接下来gcc会执行 [butbueatiful@xt myhello]$gcc-S hello.i#得到汇编文件hello.s 3.as将汇编文件编译成机器码(汇编器as): [butbueatiful@xt myhello]$as hello.s-o hello.o 得到输出文件为hello.o hello.o中为目标机器上的二进制文件

C++编译连接详解

C++程序从编译到链接然后再到调用的整个过程如下: 注:这里只是研究C++的主流编译过程,与Java没有任何关系,因为使用的技术完全不一样(Java是编译和解释结合的语言)。并且由于不同的编译器厂商对于程序的编译过程不尽相同,但是主要流程还是一样的。 其实长久以来我就一直很不清楚obj文件的内容到底是什么,有人说是汇编,有人说是机器语言。如果是机器语言的话,那编译的过程是怎样加入操作系统信息的呢?因为这个问题的不断扩展和困扰,便决定彻底研究一下,网上几乎找不到相关资料,作者参照了基本系统编程的书籍后自行整理而来,数目见底,仅供参考,欢迎讨论。 一个C++工程中会存在cpp文件,头文件,库文件。 1.首先经历的是预处理过程,将头文件加载进来,并且将各种#define信息代入。这时会见不到头文件,工程经过处理后会生成以cpp文件为基础的编译单元。有人可能会问那么头文件到哪里去了。其实头文件将cpp文件中的#include替换掉了。因此在以后的编程中需要严格注意include的先后顺序。因为C++语言是一种很注重申明的语言,为什么会这样这与程序的编译过程和链接过程的算法有关。貌似话题有点转远了,其实在这个阶段

是生成一个个独力的编译单元。 2.在编译单元生成之后,便是将编译单元进行编译,其实对于主流的编译其实存在两个阶段,首先是生成汇编语言,然后使用汇编器生成机器语言。其实这里要讲解的是汇编语言怎么变成机器语言的呢。机器语言顾名思义就是0101的二进制代码。对于一个类似于MOV AX,BX(这里写的是Intel 80x86的汇编代码,其实几乎每一种不同架构的芯片的汇编语言不怎么一样)的代码而言就是将MOV和AX和BX原封不动的用0101替换掉,如MOV 代码是35的话AX为01,BX为10的话翻译的机器代码就是350110,二进制也就是001101010000000100010000。 3.接下来的任务是链接。链接的过程如下所示: 因为篇幅太长,请看附件。 其实链接的任务是生成可执行文件。 其实我的一些不确认也就在这个地方。其实每一个程序都肯定有操作系统的一些信息,比如说程序的运行环境是DOS还是Windows程序,程序的大小等。我认为编译的整个过程中应该是在最后生成可执行文件的时候加入的。 以上便是对于编译,链接的整个过程。个人意见,仅作参考。

linux下编写c源程序并编译运行

姓名:雨田河南大学rjxy 班级:XXXX 实验二Linux基本操作 实验二Linux基本操作 编写c源程序并用编译运行 【需求】 ◆在当前目录下创建新文件t.c,用vi编辑器一段简单代码,代码要求在屏幕上输出 文字“Hello Linux!”; ◆用gcc编译t.c文件,并运行,查看输出结果,若结果错误,请根据提示修改;【系统及软件环境】 操作系统:Virtualbox,Fedora 13 【实验配置文件及命令】 1.配置文件: 2.命令:touch、rpm、gcc、./等

进入Linux操作系统,应用程序-> 系统工具-> 终端,输入命令:su 输入密码切换到root超级用户。 1.在当前目录建立一个新的目录test:$ mkdir test 在test目录下建立文件t.c :$touch t.c 3编辑程序源代码:vi t.c 首先按下键盘的“i”键,字符界面下方出现“insert”提示字符,此时输入以下代码: #include "stdio.h" int main() { printf("Hello Linux!\n"); return 0; } 4 保存退出:先按下“Esc”键,然后按下“shift”和“:”键,界面上出现冒号,然后输入“xq!”或者“x”对代码保存退出。 5 由于系统默认没有安装C语言编译程序,下面进行安装gcc 程序; 此处不再赘述,以下引用实验指导书: 1.gcc的安装 (1)查看gcc是否安装 rpm –q gcc (2)指定安装源 在“系统-分配光驱”里选择“Fedora-13-i386-DVD.iso” (3)查看安装源挂载位置 df命令,可查看到虚拟光驱挂载点 返回结果为:/media/Fedora 13 i386 DVD (4)使用安装源 安装的文件为RPM安装包,所在位置为安装光盘中的“Packages”目录下,可用“cd”命令进入此目录 cd /media/ Fedora 13 i386 DVD/Packages ★由于“Fedora 13 i386 DVD”名字中有空格,若直接输入,则会提示找不到此目录,可用“tab”键自动补全 【方法】cd /media/F)/P() 则可返回如下结果: cd /media/Fedora\ 13\ i386 \DVD\ /Packages (5)查看当前目录下是否有gcc安装包

C语言程序设计慕课下编译答案(完美)

1 求最大公约数和最小公倍数(15分) #include int fun1( int m, int n) //辗转相除法求m/n的公约数 { int r; while (n) { r=m % n; m=n; n=r; } return m; } int fun2(int m,int n) //最小公倍数 { return m*n/fun1(m,n); } int main() { int a,b; scanf("%d,%d",&a,&b); //输入 printf("最大公约数:%d\n最小公倍数:%d\n",fun1(a,b),fun2(a,b)); return 0; } 2排序并插入(15分) #include void InsertionSort(int* arr, int n) // 插入排序 { int *last = 0, *next = 0, key = 0, i = 0; for (i = 1; i < n; ++i) { next = arr + i, key = *next; if (key < *arr) // 将待插的数据比第一个数据小,直接插到最前面。 { for (last = next; next != arr; next = last) *next = *--last; *arr = key; }

else // 否则从后往前遍历,找到第一个小于关键字的位置插入。 { for (last = next; key < *--last; next = last) *next = *last; *next = key; } } } void OutPutArray(const int* arr, int n) // 打印一个数组 { int i; for (i = 0; i < n; ++i) { printf("%d", arr[i]); if(i

C语言的编译链接过程的介绍

C语言的编译链接过程的介绍 发布时间:2012-10-2600:00:00来源:中国IT实验室作者:佚名 关键字:C语言 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:

从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。 编译过程 编译过程又可以分成两个阶段:编译和会汇编。 编译 编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段: 第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如 #include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。 主要是以下几方面的处理:

(1)宏定义指令,如#define a b 对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的a则不被替换。还有#undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。 (2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif 等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译 程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。 (3)头文件包含指令,如#include"FileName"或者#include等。 在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(<>)。

Make、 编译选项、连接问题

cxMake使用问题、Gcc编译选项问题、GNU 连接问题一、GNU Make使用 make工作自动确定工程的哪部分需要重新编译,执行命令去编译它们。虽然make多用于C程序,然而只要提供命令行的编译器,你可以将其用于任何语言。 如果要使用make,你必须写一个叫做―makefile‖的文件,这个文件描述工程中文件之间的关系,提供更新每个文件的命令。典型的工程是这样的:可执行文件靠目标文件来更新,目标文件靠编译源文件来更新。 Makefile写好之后,每次更改了源文件后,只要执行make就足够了,所有必要的重新编译将执行。Make程序利用makefile中的数据库和文件的最后修改时间来确定那个文件需要更新;对于需要更新的文件,make执行数据库中记录的命令。 可以提供命令行参数给make来控制那个文件需要重新编译。 1.1Makefile介绍 Makefile文件告诉make做什么,多数情况是怎样编译和链接一个程序。 这里有一个简单的makefile,描述如何编译链接由8个C文件和3个头文件组成的一个编辑器: edit : main.o kbd.o command.o display.o \ insert.o serach.o files.o utils.o cc –o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o main.o : main.c defs.h cc –c main.c kdb.o : kbd.c defs.h command.h cc –c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o 将长行用\分开便于阅读,这和使用一个长行的作用是一样的。使用这个makefile创建可执行文件“edit”时运行make就可以了;如果要将可执行文件和目标文件删除,执行make clean

c++程序链接的过程原理详解

许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误,而且通常是在使用第三方库时遇到的。对于这个问题,有的朋友可能不知其然,而有的朋友可能知其然却不知其所以然,那么本文就试图为大家彻底解开关于它的种种疑惑。 大家都知道,从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码,然后由汇编器(assembler)翻译成机器指令(再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中;(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。 编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器,而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢?编译器认为函数与初始化了的全局变量都是强符号,而未初始化的全局变量则成了弱符号。比如有这么个源文件: extern int errorno; int buf[2]={1,2}; int*p; int main() { return0; } 其中main、buf是强符号,p是弱符号,而errorno则非强非弱,因为它只是个外部变量的使用声明。 有了强弱符号的概念,我们就可以看看链接器是如何处理与选择被多次定义过的全局符号: 规则1:不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号); 规则2:如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么选择强符号; 规则3:如果一个符号在所有目标文件中都是弱符号,那么选择其中任意一个; 由上可知多个目标文件不能重复定义同名的函数与初始化了的全局变量,否则必然导致LNK2005和LNK1169两种链接错误。可是,有的时候我们并没有在自己的程序中发现这样的重定义现象,却也遇到了此种链接错误,这又是何解?嗯,问题稍微有点儿复杂,容我慢慢道来。 众所周知,ANSI C/C++定义了相当多的标准函数,而它们又分布在许多不同的目标文件中,如果直接以目标文件的形式提供给程序员使用的话,就需要他们确切地知道哪个函数存在于哪个目标文件中,并且在链接时显式地指定目标文件名才能成功地生成可执行文件,显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制,这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名,链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块,并把(且只把)它们从库中拷贝出来

C语言编译过程总结详解

C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下: 从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。 编译过程 编译过程又可以分成两个阶段:编译和会汇编。 编译 编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段: 第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。 主要是以下几方面的处理: (1)宏定义指令,如 #define a? b 对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a 则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。 (2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。 (3)头文件包含指令,如#include "FileName"或者#include 等。 在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。 (4)特殊符号,预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。 预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。 第二个阶段编译、优化阶段,经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。

C语言编译全过程

编译的概念:编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。 编译的完整过程:C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等) 1. 编译预处理 读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理 伪指令主要包括以下四个方面: (1)宏定义指令,如#define Name TokenString,#undef等。 对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。 (2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉 (3)头文件包含指令,如#include "FileName"或者#include 等。 在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。 采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。 包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。 (4)特殊符号,预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。 预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。 2. 编译、优化阶段 经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语

编译链接DPDK库文件的方法

本文假设已经成功编译出了DPDK的库文件。 虚拟机操作系统:CentOS 7 x86_64,系统中已经安装了RealTime 内核。 DPDK版本:17.11.1 DPDK库文件编译目录:/root/dpdk-stable-17.11.1。 系统环境变量的设置:(在编译自定义库文件前,要确认这两个环境变量设置正确) export RTE_SDK=/root/dpdk-stable-17.11.1 export RTE_TARGET=x86_64-native-linuxapp-gcc 自定义库文件目录:/home/zack/SelfDefLib。 自定义文件:self_def_lib.c, self_def_lib.h, Makefile, 位于上述自定义库文件目录下。 self_def_lib.h 代码如下: /***************************************************************************** Copyright (c) 2018 ABC.Co.Ltd. All rights reserved. FileName: self_def_lib.h Auth: Zack Date: 2018/06/24 Discription: Declaration of self-defined functions to call DPDK interface. ******************************************************************************/ #ifndef __self_def_lib_h__ #define __self_def_lib_h__ #ifdef __cplusplus extern "C"{ #endif #include #include #include #include /* DPDK EAL init */ int exam_dpdk_init(int argc, char **argv); #ifdef __cplusplus } #endif

GCC 动态与静态链接的编译

GCC 动态链接与静态连接的编译 根据链接时期的不同,库又有静态库和动态库之分,有别于静态库,动态库的链接是在程序执行的时候被链接的。 1 库的分类 根据链接时期的不同,库又有静态库和动态库之分。 静态库是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行。 有别于静态库,动态库的链接是在程序执行的时候被链接的。所以,即使程序编译完,库仍须保留在系统上,以供程序运行时调用。(TODO:链接动态库时链接阶段到底做了什么) 2 静态库和动态库的比较 链接静态库其实从某种意义上来说也是一种粘贴复制,只不过它操作的对象是目标代码而不是源码而已。因为静态库被链接后库就直接嵌入可执行文件中了,这样就带来了两个问题。 首先就是系统空间被浪费了。这是显而易见的,想象一下,如果多个程序链接了同一个库,则每一个生成的可执行文件就都会有一个库的副本,必然会浪费系统空间。 再者,人非圣贤,即使是精心调试的库,也难免会有错。一旦发现了

库中有bug,挽救起来就比较麻烦了。必须一一把链接该库的程序找出来,然后重新编译。 而动态库的出现正弥补了静态库的以上弊端。因为动态库是在程序运行时被链接的,所以磁盘上只须保留一份副本,因此节约了磁盘空间。如果发现了bug或要升级也很简单,只要用新的库把原来的替换掉就行了。 那么,是不是静态库就一无是处了呢? 答曰:非也非也。不是有句话么:存在即是合理。静态库既然没有湮没在滔滔的历史长河中,就必然有它的用武之地。想象一下这样的情况:如果你用libpcap库编了一个程序,要给被人运行,而他的系统上没有装pcap库,该怎么解决呢?最简单的办法就是编译该程序时把所有要链接的库都链接它们的静态库,这样,就可以在别人的系统上直接运行该程序了。 所谓有得必有失,正因为动态库在程序运行时被链接,故程序的运行速度和链接静态库的版本相比必然会打折扣。然而瑕不掩瑜,动态库的不足相对于它带来的好处在现今硬件下简直是微不足道的,所以链接程序在链接时一般是优先链接动态库的,除非用-static参数指定链接静态库。

C程序编译过程详解

C程序编译过程详解 概述: C语言编译的整个过程是非常复杂的,里面涉及到的编译器知识、硬件知识、工具链知识都是非常多的,深入了解整个编译过程对工程师理解应用程序的编写是有很大帮助的,希望大家可以多了解一些,在遇到问题时多思考、多实践。一般情况下,我们只需要知道分成编译和连接两个阶段,编译阶段将源程序(*.c)转换成为目标代码(,一般是obj文件,至于具体过程就是上面说的那些阶段),连接阶段是把源程序转换成的目标代码(obj文件)与你程序里面调用的库函数对应的代码连接起来形成对应的可执行文件(exe文件)就可以了,其他的都需要在实践中多多体会才能有更深的理解。 一. 简单解释: 程序的编译过程如上图所示,分为预处理、编译、汇编、链接等几个阶段。 预处理:预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。 编译:将得到的i文件翻译成汇编代码。s文件。 汇编:将汇编文件翻译成机器指令,并打包成可重定位目标程序的O文件。该文件是二进制文件,字节编码是机器指令。 链接:将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件. 二.详细解释 编译的概念:编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。 编译的完整过程:C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等) 编译,编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。 C源程序头文件-->预编译处理(cpp)-->编译程序本身-->优化程序-->汇编程序-->链接程序-->可执行文件 1.编译预处理 读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理

C++编译器与连接器的工作原理

C++编译器与链接器工作原理 1. 几个概念 1)编译:把源文件中的源代码翻译成机器语言,保存到目标文件中。如果编译通过,就会把CPP转换成OBJ文件。 2)编译单元:根据C++标准,每一个CPP文件就是一个编译单元。每个编译 单元之间是相互独立并且互相不可知。 3)目标文件:编译所生成的文件,以机器码的形式包含了编译单元里所有的代码和数据。 还有一些其他信息,如未解决符号表,导出符号表和地址重定向表等。目标文件是以二进制的形式存在的。 根据C++标准,一个编译单元(Translation Unit)是指一个.cpp文件以及这所include的所有.h文件,.h文件里面的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件,后者拥有PE(Portable Executable,即Windows可执行文件)文件格式,并且本身包含的就是二进制代码,但是不一定能执行,因为并不能保证其中一定有main函数。当编译器将一个工程里的所有.cpp文件以分离的方式编译完毕后,再由链接器进行链接成为一个.exe 或.dll文件。 2. 分析编译的过程 源文件:A.cpp int n = 1; void FunA() { ++n; } 目标文件:A.obj

偏移量内容长度 0x0000 n 4 0x0004 FunA ?? 注意:这只是说明,与实际目标文件的布局可能不一样,??表示长度未知,目标文件的各个数据可能不是连续的,也不一定是从0x0000开始。 FunA函数的内容可能如下: 0x0004 inc DWORD PTR[0x0000] 0x00?? ret 这时++n已经被翻译成inc DWORD PTR[0x0000],也就是说把本单元0x0000位置的一个DWORD(4字节)加1。 源文件:B.cpp extern int n; void FunB() { ++n; } 目标文件:B.obj 偏移量内容长度 0x0000 FunB ?? 这里为什么没有n的空间呢,因为n被声明为extern,这个extern关键字就是告诉编译器n已经在别的编译单元里定义了,在这个单元里就不要定义了。由于编译单元之间是互不相关的,所以编译器就不知道n究竟在哪里,所以在函数FunB就没有办法生成n的地址,那么函数FunB中就是这样的: 0x0000 inc DWORD PTR[????]

Linux下用gcc编译c程序

Linux下用gcc编译c程序 GCC 支持了许多不同的语言,包括C、C++、Ada、Fortran、Objective C,Perl、Python 和Ruby,甚至还有Java。 Linux 内核和许多其他自由软件以及开放源码应用程序都是用C 语言编写并使用GCC 编译的。 编译C++程序: -c 只编译不连接 g++ file1 -c -o file1.o g++ file2 -c -o file2.o g++ file1.o file.o -o exec g++ -c a.cpp 编译 g++ -o a a.o 生成可执行文件 也可以g++ -o a a.cpp直接生成可执行文件。 1. 编译单个源文件 为了进行测试,你可以创建“Hello World”程序: #include #include int main(int argc, char **argv) { printf(“Hello world!\n”); exit(0); }

使用如下命令编译并测试这个代码: # gcc -o hello hello.c # ./hello Hello wordl! 在默认情况下产生的可执行程序名为a.out,但你通常可以通过gcc 的“-o”选项来指定自己的可执行程序名称。 2. 编译多个源文件 源文件message.c包含一个简单的消息打印函数: #include void goodbye_world(void) { p rintf(“Goodbye, world!\n”); } 使用gcc的“-c”标记来编译支持库代码: # gcc -c message.c 这一过程的输出结果是一个名为message.o的文件,它包含适合连接到一个较大程序的已编译目标代码。 创建一个简单的示例程序,它包含一个调用goodbye_world的main函数 #include void goodbye_world(void): int main(int argc, char **argv) { goodbye_world();

相关主题
文本预览
相关文档 最新文档