网站标题title为什么不能频繁的改,上海建交人才网官网,做棋牌网站建设哪家好,东莞高端建站公司目录
1 程序的编译过程
2 动态链接的优缺点
2.1 动态链接的优点
2.2 动态链接的缺点
2.3 只使用动态链接
3 函数库链接的5个特殊秘密
4 警惕Interpositioning
5 产生链接器报告文件 1 程序的编译过程
程序的编译过程是将源代码转换成计算机可以执行的机器代码的过程。…目录
1 程序的编译过程
2 动态链接的优缺点
2.1 动态链接的优点
2.2 动态链接的缺点
2.3 只使用动态链接
3 函数库链接的5个特殊秘密
4 警惕Interpositioning
5 产生链接器报告文件 1 程序的编译过程
程序的编译过程是将源代码转换成计算机可以执行的机器代码的过程。这个过程通常包括以下几个主要步骤 1预处理Preprocessing预处理是编译过程的起始步骤主要针对源代码文件中的预处理指令进行相应处理。
宏定义展开#define通过#define指令定义宏预处理时会按照定义规则对代码里相应的宏进行文本替换比如定义#define PI 3.14代码中出现PI的地方就会被替换成3.14。条件编译指令#ifdef、#ifndef、#endif 等条件编译指令可依据是否定义了特定宏等条件来决定代码段是否参与后续编译。包含头文件#include预处理器会把指定头文件的全部内容插入到源代码中相应的位置。例如#include stdio.h会将标准输入输出头文件里的内容像printf、scanf等函数的声明等添加进来。
2编译Compilation将预处理后的源代码转换为中间代码并进行优化以及最终生成目标代码的过程涉及多个子环节。
词法分析编译器会把预处理后的源代码当作字符流将其拆解成一个个单词tokens这些单词涵盖了编程语言里的关键字如 C 语言中的int、if、for等、标识符变量名、函数名等、常量整数常量、浮点数常量、字符常量等、运算符、-、*、/等以及界符;、{、}等。例如对于代码int num 10;词法分析器能准确分解出 “int”关键字、“num”标识符、“”运算符、“10”常量和 “;”界符为后续分析提供基础元素。语法分析基于词法分析得到的单词编译器依据编程语言既定的语法规则构建语法树以树形结构清晰呈现程序的语法构成以及语句、表达式之间的层次关系。比如针对if语句if (condition) { statement; }语法树会将if作为根节点其下细分出条件表达式condition节点以及语句块statement节点。一旦代码存在语法错误像括号缺失或者关键字拼写有误等情况语法分析阶段就能检测出来。语义分析主要检查程序语义的正确性包含检查变量是否正确定义与使用、类型是否匹配等关键方面。例如在表达式int a 3.14;中语义分析环节会察觉到将双精度浮点数赋值给整型变量属于类型不匹配的错误同时也会处理变量的作用域问题确保变量仅在其定义的有效范围内被正确访问例如函数内部定义的局部变量不能在函数外部随意调用。中间代码生成部分编译器会生成中间代码它处于源语言与目标机器语言之间具备平台无关性优势常见形式有三地址码等。例如对于表达式a b c可能生成类似t1 b c; a t1的三地址码形式这种中间表示形式利于后续进一步的优化以及适配不同目标机器的代码生成工作。代码优化编译器会对生成的中间代码实施优化操作旨在提升程序的执行效率优化手段多种多样。比如常量折叠像int a 2 3;可直接优化为int a 5;减少不必要的运算死代码消除即去除那些永远不会被执行的代码避免占用资源循环展开通过将循环体展开一定次数来降低循环控制方面的开销等通过这些优化策略让程序在执行时间和存储空间利用上更为高效。目标代码生成这是编译阶段的收尾环节会把经过优化的中间代码若有或者直接把处理后的代码转换为目标机器的语言这种目标代码可能是汇编语言形式也有可能直接就是机器代码二进制形式具体取决于编译器的设计以及编译的配置。
3汇编Assembly汇编器Assembler将汇编语言代码转换成机器代码。汇编语言是一种低级语言它更接近于机器代码但仍然包含一些助记符使得程序员更容易理解和编写。
4链接Linking链接过程旨在将编译生成的多个目标文件通常以.o在 Linux 等系统下或.obj在 Windows 系统下为扩展名与各类库文件以及其他相关目标文件进行合并整合最终生成一个单一的可执行文件像.exeWindows 系统或.outLinux 等系统文件。
符号解析目标文件中包含诸多符号例如函数名、全局变量名等链接器的关键任务之一就是确定这些符号的具体定义位置。比如一个源文件中调用了另一个源文件里定义的函数int add(int a, int b)在链接时链接器必须精准找到该函数实现代码所在的目标文件若无法找到某个符号的定义就会出现如 “undefined reference to add” 这类链接错误提示。重定位目标文件中的代码与数据部分往往包含一些相对地址或者虚拟地址在链接阶段需要依照最终可执行文件的整体布局对这些地址进行相应调整。例如目标文件中某个函数调用指令原本采用相对地址来指向被调用函数当链接器把多个目标文件组合到一起时就需要重新计算这些相对地址以保障函数调用能够准确无误地跳转到被调用函数的实际内存位置确保程序执行的正确性。库链接程序在开发过程中通常会借助一些库来实现额外的功能像标准 C 库、数学库等链接器要把这些库文件与目标文件妥善链接起来。库分为静态库和动态库两种类型静态库是多个目标文件的集合链接时链接器会把程序实际使用的静态库中目标文件的代码和数据全部复制到最终可执行文件里。动态库链接时不会把库文件全部代码复制到可执行文件中而是在程序运行需调用其函数时由操作系统加载动态库并完成相应函数调用操作。
5加载Loading加载器Loader负责将可执行文件加载到内存中进而启动程序的执行流程。
2 动态链接的优缺点
2.1 动态链接的优点
动态链接是一种更为现代的方法能够更加有效地利用磁盘空间动态链接只需要处理对动态库的引用关系不需要像静态链接那样将库的全部代码合并到可执行文件中因此在编译和链接阶段时间也会缩短。尽管单个可执行文件约启动速度稍受影响但动态链接可以从两个方面提高性能
1动态链接可执行文件比功能相同的静态链接可执行文件的体积小相应的会节省磁盘空间和虚拟内存。静态链接在生成可执行文件时会把程序所用到的函数库比如标准 C 库、数学库等中相关目标文件的代码和数据全部复制到可执行文件里面即便它可能只调用了库中的少数几个函数而动态链接则不同动态链接的可执行文件中只是包含了对相应动态库中函数和变量等的引用信息真正的函数库代码并不嵌入到可执行文件里。
2当多个可执行文件都动态链接到同一个特定函数库时在运行期间操作系统只会把这个函数库加载到内存中一次形成一个单独的拷贝。例如在 Linux 系统下多个应用程序都动态链接了libc.soC 标准库对应的动态库那么在内存中只会存在一份libc.so的实例。操作系统内核通过内存映射机制来实现共享它会将这份已加载到内存中的函数库映射到每个需要使用它的进程的虚拟地址空间中。各个进程虽然感觉像是自己独占了这个函数库但实际上它们共享的是同一份物理内存中的代码和数据这样就避免了重复加载相同的函数库内容到内存里。如果可执行文件是静态链接的每个文件都将拥有一份函数库的拷贝显然极为浪费。
动态链接使得函数库的版本升级更为容易。新的函数库可以随时发布只要安装到系统中旧的程序就能够自动获得新版本函数库的优点而无需重新链接。
2.2 动态链接的缺点
动态链接是一种“just-in-time(JIT)”链接这意味着程序在运行时必须能够找到它们所需要的函数库。链接器通过把库文件名或路径名植入可执行文件中来做到这一点。这意味着函数库的路径不能随意移动。如果把程序链接到/user/ib/libthread.so库那么就不能把该函数库移动到其他的目录除非在链接器中进行特别说明。否则当程序调用该函数库的函数时就会在运行时导致失败给出这样一条错误信息
ld.so.1:main:fatal:libthread.so:cant open file:errno 2
当在一台机器上编译完程序后把它拿到另一台不同的机器上运行时也可能出现这种
情况。执行程序的机器必须具有所有该程序需要链接的函数库而且这些函数库必须位于在链接器中所说明的目录。对于标准系统函数库而言这并不成问题。
2.3 只使用动态链接
动态链接现在是运行System V release4UNIX的计算机所采用的缺省设置。从作用上
看静态链接现已过时只能静静躺在一边睡大觉。
使用静态链接的最大危险在于将来版本的操作系统可能与可执行文件所绑定的系统函数库不兼容。如果应用程序静态链接于版本N的操作系统中当把程序运行于版本N1的操作系统上时它可能会立即崩溃也可能出现一个不明显的错误。我们无法保证早期版本的系统函数库能够在后期版本的系统上正确地运行。事实上反过来考虑倒还比较保险一点。但是如果应用程序动态链接到版本N的系统函数库当它运行于版本N1的操作系统上时它就会正确选取N1版本的系统函数库。
相反静态链接的应用程序不得不针对每个新版本的操作系统进行重新生成以保证能够运行。而且有些函数库(扣libaio.so,libdl.so,libsys..so,libsolv.so以及librpcsvc.so等)只能以动态链接的形式使用。如果在应用程序中使用了这些函数库中的任何一个你的程序就必须使用动态链接。最好的策略就是所有的应用程序都使用动态链接这就可以避免可能产生的问题。
3 函数库链接的5个特殊秘密
当使用函数库时需要掌握5个基本的、不明显的约定。绝大多数C语言书籍或手册对此并没有作出清楚的解释。
1动态库文件的扩展名是“.so”而静态库文件的扩展名是“.a”
按照约定所有动态库的文件名的形式是libname.so。这样线程函数库便被称作libthread.so。静态库的文件名形式是libname.a共享archive的文件名形式是libname.sa。共享archive只是一种过渡形式帮助人们从静态库转变到动态库现在已过时。
2通过-lthread选项告诉编译链接到libthread.so
传给C编译器的命令行参数里并没有提到函数库的完整路径名甚至没有提到在函数库目录中该文件的完整名字函数库名字的呈现形式是把 “lib” 部分和文件的扩展名去掉然后在前面添加一个 “l”。例如对于名为 libthread.so 的函数库在命令行中就是通过 -lthread 选项来告知编译器去链接它。 gcc -o thread_demo thread_demo.c -lthread
3编译器期望在确定的目录找到库
这里你可能会疑惑编译器是怎么知道该往什么目录寻找函数库呢就像存在一种特殊的规则用于查找头文件一样编译器也自有办法来寻找函数库。它查看一些特殊的位置如在/usr/ib中查找函数库例如线程库位于usr/Iib/libthread.so。
编译器选项-Lpathname告诉链接器一些其他的目录如果命令中加入了-l选项链接器就往这些目录查找函数库。系统中存在几个环境变量LD LIBRARY_PATH和
LD_RUN_PATH,也是用于提供这类信息。出于安全性、性能和创建/运行独立性方面的考虑使用环境变量的做法现在己经不提倡。一般还是在链接时使用-Lpathname和-Rpathname选项。
4观察头文件确认所使用的函数库
你有可能遇见的另一个关键问题是“我怎么知道必须链接到哪些函数库如果观察程序中的源代码就会发现自己调用了一些自己不曾实现的函数。例如如果程序跟三角有关可能会调用像sin()和cos()这样的函数它们可以在math函数库中找到。
一个很好的建议就是可以观察程序所使片的#include指令。在程序中所包舍的每个头文件都可能代表一个必须链接的库但需要注意头文件的名字通常并不与它所对应的函数库名相似。函数库链接所存在的另一个不一致性就是函数库所包含的某个函数的原型可能与其他头文件中所声明的函数的原型一样。
5与提取动态库中的符号相比静态库中的符号提取的方法限制更严
最后在动态链接和静态链接的链接语义上还存在一个额外的大区别它经常会迷惑不够仔细的用户。
假设我们有一个简单的 main.c 文件里面调用了一些来自标准 C 库的函数比如 printf 函数其所在的库在动态链接时会被自动处理使用 gcc 编译器进行动态链接编译生成可执行文件的命令示例如下
gcc main.c -o dynamic_executable
这里-o 选项指定了输出的可执行文件名是 dynamic_executable。在这个过程中像 printf 这类库符号所在的动态库例如 libc.so 等会在运行时被加载到程序的虚拟地址空间使得程序可以找到并执行相应的函数并且多个链接在一起的文件都可以访问到这些动态库中的符号。
同样针对上述 main.c 文件进行静态链接并且假设有一个自定义的静态库 mylib.a同时假设 main.c 中调用了 mylib.a 里定义的函数以及标准 C 库中的函数比如 printf按照正确顺序进行静态链接编译生成可执行文件的命令示例如下
gcc main.c mylib.a -o static_executable
这里必须保证先写 main.c 再写 mylib.a 因为静态链接时符号是从左到右按顺序解析的如果写成
gcc mylib.a main.c -o static_executable
可能就会出现问题因为在处理 mylib.a 这个静态库时链接器一开始还没遇到 main.c 里对 mylib.a 中符号的未定义引用也就是还不知道需要从 mylib.a 里找哪些符号那么就可能不会正确地从 mylib.a 中提取对应的符号导致后续链接 main.c 时出现符号未定义等错误。
如果在自己的代码之前引入静态库又会带来另一个问题。因为此时尚未出现未定义的符号所以它不会从函数库中提取任何符号。接着当目标文件被链接器处理时它所有的对函数库的引用都将是未实现的
例如像 “cc -lm main.c” 这样进行静态链接math 库常以静态链接的 archive 形式存在用于提高运行时性能若程序使用了如 sin 等数学函数会得到 “Undefined first referenced symbol in file sin main.o ld:fatal:Symbol referencing errors.No output written to a.out” 这样的错误信息。
为从 math 库中提取所需符号需要写成 “cc main.c -lm” 这种形式让文件先包含未解析的引用。个人都习惯了通用的命令形式命令选项文件所以让链接器采用命令文件选项这样的约定是很容易引起混淆。
4 警惕Interpositioning
Interpositioning 是一种通过编写与函数同名的函数来取代库函数行为的技术。可以在特定程序中拦截库函数的调用以便检查参数、返回值或跟踪程序的执行流程有助于快速定位问题。在某些情况下可以通过优化同名的用户函数来提高特定操作的执行效率。
使用Interpositioning需要格外小心。很容易发生自己代码中某个符号的定义取代函数库中的相同符号的意外。这意味着不仅自己对该库函数的调用会被自己版本的函数调用所取代而且所有调用该库函数的系统调用也会被用户函数取代。例如假设程序中使用了一个库函数 printf 进行输出而程序员不小心编写了一个同名的 printf 函数。在这种情况下程序中所有原本应该调用库函数 printf 的地方都会调用用户自定义的 printf 函数这可能会导致输出结果与预期不符甚至可能引发程序错误。
当编译器注意到库函数被另外一个定义覆盖时它通常不会给出错误信息。这是因为 C 语言遵循“程序员所做的都是对的”的设计哲学编译器认为这是程序员的意图。这种特性使得在使用 Interpositioning 时错误很难被及时发现。程序员可能在不经意间使用了这种技术却没有意识到自己已经覆盖了库函数从而导致程序出现难以察觉的错误。
由于 Interpositioning 具有较高的风险只有在确实需要进行调试或提高效率时才考虑使用。对于新手来说应尽量避免使用这种技术以免伤害自己。
5 产生链接器报告文件
可以在ld程序中使用“-m”选项让链接器产生一个报告里面包括了被Interpose
的符号的说明。通常带“-m”选项的ld会产生一个内存映射或列表显示在可执行文件中的什么地方放入了哪些符号。它同时显示了同一个符号的多个实例通过查看报告的内容用户可以判断是否发生了Interpositioning。假如你有一个待链接的目标文件叫 main.o想生成包含符号相关信息的报告命令可能如下
ld -m main.o -o output_executable
ld程序中的“-D”选项是随SunOS5.3引入的日的是提供更好的链接-编辑调试。这个选项允许用户显示链接-编辑过程和所包含的输入文件。如果需要监视从archive中提取对象的过程这个选项尤其有用同时可用于显示运行时绑定信息。同样针对 main.o 这个目标文件想要查看链接编辑过程和涉及的输入文件命令示例如下
ld -D main.o -o another_executable
ld是一个复杂的程序还有很多其他选项和约定未在此处说明。对于绝大多数应用来说这些说明已经足够了。如需知道更多有关它的知识下面提供了四条途径按其复杂程度分列如下
使用1dd命令列出可执行文件的动态依赖集。这条命令会告诉你动态链接的程序所需要的函数库。ld程序的-Dhelp选项能提供一些信息有助于查找链接过程中出现的问题。查看ld程序的在线文档。