【转】编译,链接与库的使用(1)

本文详细介绍了从源代码到可执行文件的编译过程,包括预编译、编译、汇编和链接。重点讲解了链接的符号解析和重定位,以及静态链接和动态链接的区别。在静态链接中,提到了链接器如何处理符号解析和重定位,以及静态库的使用。动态链接部分,讨论了共享库的使用、手动载入共享库的方法,以及动态库在程序运行时的加载和链接。此外,还讨论了不同编译器版本、符号表冲突和弱链接的概念。
摘要由CSDN通过智能技术生成

背景

为什么使用ullib有时会出现 undefined reference error 的错误?

为什么在动态链接库里ul_log会把日志输出到屏幕上?

为什么用-static 编译有时候会报warning?

我们在使用基础库或者第三方库的时候,经常遇到这样那样的问题,本文结合公司目前的主要环境,说明库的原理,使用的注意事项。

从程序到可执行文件

从hello world 说起

#includeint main() { printf("hello world\n"); return 0; } 
上面这一段程序任何一个学过C语言的同学都是闭着眼睛都可以写出来,但是对于将这样一个源代码编译成为一个可执行文件的过程却不一定有所了解。

上面的程序如果要编译,很简单

gcc hello.c

然后./a.out就可以运行,但是在这个简单的命令后面隐藏了许多复杂的过程

一般来说,可以把这样的过程分成4个, 预编译, 编译, 汇编和链接

预编译

这个过程包括了下面的步骤

  1. 宏定义展开,所有的#define 在这个阶段都会被展开
  2. 预编译命令的处理,包括#if #ifdef 一类的命令
  3. 展开#include 的文件,像上面hello world 中的stdio.h , 把stdio.h中的所有代码合并到hello.c中
  4. 去掉注释
gcc的预编译 采用的是预编译器cpp, 我们可以通过-E参数来看预编译的结果,如:
gcc -E hello.c -o hello.i

生 成的 hello.i 就是经过了预编译的结果 在预编译的过程中不会太多的检查与预编译无关的语法(#ifdef 之类的还是需要检查, #include文件路径需要检查), 但是对于一些诸如 ; 漏掉的语法错误,在这个阶段都是看不出来的。 写过makefile的人都知道, 我们需要加上-Ipath 一系列的参数来标示gcc对头文件的查找路径

小提示:

1.在一些程序中由于宏的原因导致编译错误,可以通过-E把宏展开再检查错误 , 这个在编写 PHP扩展, python扩展这些大量需要使用宏的地方对于查错误很有帮助。

2. 如果在头文件中,#include 的时候带上路径在这个阶段有时候是可以省不少事情, 比如 #include , 这样在gcc的-I参数只需要指定一个路径,不会由于不小心导致,文件名正好相同出现冲突的麻烦事情. 不过公司由于早期出现了lib2和lib2-64两个目录, 以及头文件输出在include 目录下, 静态发布等一些历史原因, 有些时候使用带完整路径名的方式不是那么合适( 比如 #include 中间有一个include 显的很别扭).

不过个人认为所有的#include 都应该是尽量采用从cvs 根路径下开始写完整路径名的方式进行预编译的过程,只是受限于公司原有习惯和历史问题而显的不合适, 当然带路径的方式要多写一些代码,也是麻烦的事情, 路径由外部指定相对也会灵活一些.

编译

这个过程才是进行语法分析和词法分析的地方, 他们将我们的C/C++代码翻译成为 汇编代码, 这也是一个编译器最复杂的地方

使用命令

gcc -S hello.i -o hello.s

可 以看到gcc编译出来的汇编代码, 现代gcc编译器一般是把预编译和编译合在一起,使用cc1 的程序来完成这个过程,在我们的开发机上有些时候一些同学编译大文件的时候可以用top命令看一个cc1的进程一直在占用时间,这个时候就是程序在执行编 译过程. 后面提到的编译过程都是指 cc1的处理包括了预编译与编译.

汇编

现在C/C++代码已经成为汇编代码了,直接使用汇编代码的编译器把汇编变成机器码(注意还不是可执行的) .

gcc -c hello.c -o hello.o

这里的hello.o就是最后的机器码, 如果作为一个静态库到这里可以所已经完成了,不需要后面的过程.

对于静态库, 比如ullib, COM提供的是libullib.a, 这里的.a文件其实是多个.o 通过ar命令打包起来的, 仅仅是为了方便使用,抛开.a 直接使用.o 也是一样的

小提示:

1. gcc 采用as 进行汇编的处理过程,as 由于接收的是gcc生成的标准汇编, 在语法检查上存在不少缺陷,如果是我们自己写的汇编代码给as去处理,经常会出现很多莫名奇妙的错误.

链接

链接的过程,本质上来说是一个把所有的机器码文件组合成一个可执行的文件 上面汇编的结果得到一个.o文件, 但是这个.o要生成二执行文件只靠它自己是不行的, 它还需要一堆辅助的机器码,帮它处理与系统底层打交道的事情.

gcc -o hello hello.o 

这样就把一个.o文件链接成为了一个二进制可执行文件. 我们提供的各种库头文件在编译期使用,到了链接期就需要用-l, -L的方式来指定我们到底需要哪些库。 对于glibc中的strlen之类常用的东西编译器会帮助你去加上可以不需要手动指定。

这个地方也是本文讨论的重点, 在后面会有更详细的说明

小提示:

有些程序在编译的时候会出现 "linker input file unused because linking not done" 的提示(虽然gcc不认为是错误,这个提示还是会出现的), 这里就是把 编译和链接 使用的参数搞混了,比如

g++ -c test.cpp -I../../ullib/include -L../../ullib/lib/ -lullib

这样的写法就会导致上面的提示, 因为在编译的过程中是不需要链接的, 它们两个过程其实是独立的

静态链接

链接的过程

这里先介绍一下,链接器所做的工作

其实链接做的工作分两块: 符号解析和重定位

符号解析

符号包括了我们的程序中的被定义和引用的函数和变量信息

在命令行上使用 nm ./test

test 是用户的二进制程序,包括

可以把在二进制目标文件中符号表输出

00000000005009b8 A __bss_start00000000004004cc t call_gmon_start00000000005009b8 b completed.10000000000500788 d __CTOR_END__0000000000500780 d __CTOR_LIST__00000000005009a0 D __data_start00000000005009a0 W data_start0000000000400630 t __do_global_ctors_aux00000000004004f0 t __do_gl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值