Linux环境下的编译,链接与库的使用

上传人:人*** 文档编号:562736963 上传时间:2023-11-04 格式:DOC 页数:25 大小:94.50KB
返回 下载 相关 举报
Linux环境下的编译,链接与库的使用_第1页
第1页 / 共25页
Linux环境下的编译,链接与库的使用_第2页
第2页 / 共25页
Linux环境下的编译,链接与库的使用_第3页
第3页 / 共25页
Linux环境下的编译,链接与库的使用_第4页
第4页 / 共25页
Linux环境下的编译,链接与库的使用_第5页
第5页 / 共25页
点击查看更多>>
资源描述

《Linux环境下的编译,链接与库的使用》由会员分享,可在线阅读,更多相关《Linux环境下的编译,链接与库的使用(25页珍藏版)》请在金锄头文库上搜索。

1、.Linu*环境下的编译,与库的使用From: liu*un.org/为什么使用ullib有时会出现 undefined reference error 的错误?为什么在动态库里ul_log会把日志输出到屏幕上?为什么用-static 编译有时候会报warning“我们在使用根底库或者第三方库的时候,经常遇到这样那样的问题,本文结合公司目前的主要环境,说明库的原理,使用的考前须知。从程序到可执行文件从hello world 说起include int main() printf(“hello worldn); return 0; 上面这一段程序任何一个学过C语言的同学都是闭着眼睛都可以写出来,

2、但是对于将这样一个源代码编译成为一个可执行文件的过程却不一定有所了解。 上面的程序如果要编译,很简单gcc hello.c 然后./a.out就可以运行,但是在这个简单的命令后面隐藏了许多复杂的过程一般来说,可以把这样的过程分成4个: 预编译, 编译, 汇编和。预编译:这个过程包括了下面的步骤宏定义展开,所有的#define 在这个阶段都会被展开 预编译命令的处理,包括#if #ifdef 一类的命令 展开#include 的文件,像上面hello world 中的stdio.h , 把stdio.h中的所有代码合并到hello.c中 去掉注释 gcc的预编译 采用的是预编译器cpp, 我们可

3、以通过-E参数来看预编译的结果,如: gcc -E hello.c -o hello.i 生 成的 hello.i 就是经过了预编译的结果 在预编译的过程中不会太多的检查与预编译无关的语法(#ifdef 之类的还是需要检查, #include文件路径需要检查), 但是对于一些诸如 ; 漏掉的语法错误,在这个阶段都是看不出来的。 写过makefile的人都知道, 我们需要加上-Ipath 一系列的参数来标示gcc对头文件的查找路径小提示:1.在一些程序中由于宏的原因导致编译错误,可以通过-E把宏展开再检查错误 , 这个在编写 PHP扩展, python扩展这些大量需要使用宏的地方对于查错误很有帮

4、助。2.如果在头文件中,#include 的时候带上路径在这个阶段有时候是可以省不少事情, 比方 #include , 这样在gcc的-I参数只需要指定一个路径,不会由于不小心导致,文件名正好一样出现冲突的麻烦事情. 不过公司由于早期出现了lib2和lib2-64两个目录, 以及头文件输出在include 目录下, 静态发布等一些历史原因, 有些时候使用带完整路径名的方式不是则适宜( 比方 #include 中间有一个include 显的很别扭). 不过个人认为所有的#include 都应该是尽量采用从cvs 根路径下开场写完整路径名的方式进展预编译的过程,只是受限于公司原有习惯和历史问题而显

5、的不适宜, 当然带路径的方式要多写一些代码,也是麻烦的事情, 路径由外部指定相对也会灵活一些.编译:这个过程才是进展语法分析和词法分析的地方, 他们将我们的C/C+代码翻译成为 汇编代码, 这也是一个编译器最复杂的地方使用命令gcc -S hello.i -o hello.s 可 以看到gcc编译出来的汇编代码, 现代gcc编译器一般是把预编译和编译合在一起,使用cc1 的程序来完成这个过程,在我们的开发机上有些时候一些同学编译大文件的时候可以用top命令看一个cc1的进程一直在占用时间,这个时候就是程序在执行编 译过程. 后面提到的编译过程都是指 cc1的处理包括了预编译与编译.汇编: 现在

6、C/C+代码已经成为汇编代码了,直接使用汇编代码的编译器把汇编变成机器码注意还不是可执行的) .gcc -c hello.c -o hello.o 这里的hello.o就是最后的机器码, 如果作为一个静态库到这里可以所已经完成了,不需要后面的过程.对于静态库, 比方ullib, 提供的是libullib.a, 这里的.a文件其实是多个.o 通过ar命令打包起来的, 仅仅是为了方便使用,抛开.a 直接使用.o 也是一样的小提示:1. gcc 采用as 进展汇编的处理过程,as 由于接收的是gcc生成的标准汇编, 在语法检查上存在不少缺陷,如果是我们自己写的汇编代码给as去处理,经常会出现很多莫名

7、奇妙的错误. : 的过程,本质上来说是一个把所有的机器码文件组合成一个可执行的文件 上面汇编的结果得到一个.o文件, 但是这个.o要生成二执行文件只靠它自己是不行的, 它还需要一堆辅助的机器码,帮它处理与系统底层打交道的事情.gcc -o hello hello.o 这样就把一个.o文件成为了一个二进制可执行文件. 我们提供的各种库头文件在编译期使用,到了期就需要用-l, -L的方式来指定我们到底需要哪些库。 对于glibc中的strlen之类常用的东西编译器会帮助你去加上可以不需要手动指定。这个地方也是本文讨论的重点, 在后面会有更详细的说明小提示:有些程序在编译的时候会出现 “linker

8、 input file unused because linking not done 的提示(虽然gcc不认为是错误,这个提示还是会出现的), 这里就是把 编译和 使用的参数搞混了,比方g+ -c test.cpp -I././ullib/include -L././ullib/lib/ -lullib 这样的写法就会导致上面的提示, 因为在编译的过程中是不需要的, 它们两个过程其实是独立的静态的过程 这里先介绍一下,器所做的工作,其实做的工作分两块: 符号解析和重定位符号解析符号包括了我们的程序中的被定义和引用的函数和变量信息在命令行上使用 nm ./testtest 是用户的二进制程序,

9、包括可以把在二进制目标文件中符号表输出009b8 A bss_start004cc t call_gmon_start009b8 b pleted. d CTOR_END00780 d CTOR_LIST009a0 D data_start009a0 W data_start00630 t do_global_ctors_au*004f0 t do_global_dtors_au*009a8 D dso_handle00798 d DTOR_END00790 d DTOR_LIST007a8 D DYNAMIC009b8 A edata009c0 A end00668 T fini00780

10、A fini_array_end00780 A fini_array_start00530 t frame_dummy00778 r FRAME_END00970 D GLOBAL_OFFSET_TABLE w gmon_start U g*_personality_v0C*ABI_1. T _init00780 A _init_array_end 当然上面由nm输出的符号表可以通过编译命令去除,让人不能直接看到。器解析符号引用的方式是将每一个引用的符号与其它的目标文件(.o)的符号表中一个符号的定义联系起来,对于那些和引用定义在一样模块的本地符号(注:static修饰的),编译器在编译期就可

11、以发现问题,但是对于那些全局的符号引用就比拟麻烦了下面来看一个最简单程序:includeint foo();int main() foo(); return 0; 我们把文件命名为test.cpp, 采用下面的方式进展编译 g+ -c test.cppg+ -o test test.o 第一步正常完毕,并且生成了test.o文件,到第二步的时候报了如下的错误test.o(.te*t+0*5): In function main: undefined reference tofoo()collect2: ld returned 1 e*it status 由于foo 是全局符号, 在编译的时候不

12、会报错,等到的时候,发现没有找到对应的符号,就会报出上面的错误。但是如果我们把上面的写法改成下面这样include/注意这里的static static int foo();int main() foo(); return 0; 在运行 g+ -c test.cpp, 马上就报出下面的错误:test.cpp:19: error: int foo() used but never defined 在编译器就发现foo 无法生成目标文件的符号表,可以马上报错,对于一些本地使用的函数使用static一方面可以防止符号污染,另一方面也可以让编译器尽快的发现错误在我们的根底库中提供的都是一系列的.a文件

13、,这些.a文件其实是一批的目标文件(.o)的打包结果这样的目的是可以方便的使用已有代码生成的结果,一般情况下是一个.c/.cpp文件生成一个.o文件,在编译的时候如果带上一堆的.o文件显的很不方便,像:g+ -o main main.cpp a.o b.o c.o 这样大量的使用.o也很容易出错,在linu*下使用archive来将这些.o存档和打包所以我们就可以把编译参数写成g+ -o main main.cpp ./libullib.a 我们可以使用./libullib.a 直接使用libullib.a这个库,不过gcc提供了另外的方式来使用:g+ -o main main.cpp -L.

14、/ -lullib -L指定需要查找的库文件的路径, -l 选择需要使用的库名字,不过库的名字需要用lib+name的方式命名,才会被gcc认出来 不过上面的这种方式存在一个问题就是不区分动态库和静态库,这个问题在后面介绍动态库的时候还会提到当存在多个.a ,并且在库之间也存在依赖关系,这个时候情况就比拟复杂如果我们要使用lib2-64/dict, dict又依赖ullib, 这个时候需要写成类似下面的形式g+ -o main main.cpp -L./lib2-64/dict/lib -L./lib2-64/ullib/lib -ldict -lullib -lullib 需要写在-ldic

15、t的后面,这是由于在默认情况对于符号表的解析和查找工作是由后往前(部实现是一个类似堆栈的尾递归) 所以当所使用的库本身存在依赖关系的时候,越是根底的库就越是需要放到后面否则如果上面把-ldict -lulib的位置换一下,可能就会出现 undefined reference to* 的错误 一般来说对于根底库的依赖关系可以在平台上获取, 假设存在一些第三方的依赖,就只有参考相关的帮助说明了当然gcc提供了另外的方式的来解决这个问题g+ -o main main.cpp -L./lib2-64/dict/lib -L./lib2-64/ullib/lib-*linker “-( -ldict -lullib-*linker “-)可以看到我们需要的库被-*linker “-(“和-*linker “-) 包含起来,gcc在这里处理的时候会循环自动查找依赖关系,不过这样的代价就是延长gcc的编译时间,如果使用的库非常的多时候,对编译的耗时影响还是非常大.-*linker有时候也简写成-Wl, “,它的意思是 它后面的参数是给器使用的-*linker 和 -Wl 的区别是一个后面跟的参数是

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 商业/管理/HR > 商业计划书

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号