linux使用gcc编译c语言共享库步骤

对任何程序员来说库都是必不可少的。所谓的库是指已经编译好的供你使用的代码。它们常常提供一些通用功能,例如链表和二叉树可以用来保存任何数据,或者是一个特定的功能例如一个数据库服务器的接口,就像MySQL。

大部分大型的软件项目都会包含若干组件,其中一些你发现可以用在其他项目中,又或者你仅仅出于组织目的将不同组件分离出来。当你有一套可复用的并且逻辑清晰的函数时,将其构建为一个库会十分有用,这样你就不将这些源代码拷贝到你的源代码中,而且每次都要再次编译它们。除此之外,你还可以保证你的程序各模块隔离,这样你修改其中一个模块时也不会影响到其他的模块。一旦你写好一个模块并且通过测试,你就可以无限次地安全地复用它,这可以节省大量时间和麻烦。
构建静态库太简单了,对此我们几乎不会遇到什么问题。我不想说明如何构建静态库。在此我只讨论共享库,因为对大多数人来说它更加难懂。

在我们正式开始前,让我们列一下纲要来了解从源代码到运行程序之间发生了什么:

预处理:这个阶段处理所有预处理指令。基本上就是源代码中所有以#开始的行,例如#define和#include。
编译:一旦源文件预处理完毕,接下来就是编译。因为许多人提到编译时都是指整个程序构建过程,因此本步骤也称作“compilation proper”。本步骤将.c文件转换为.o文件。
连接:到这一步就该将你所有的对象文件和库串联起来使之成为最后的可运行程序。需要注意的是,静态库实际上已经植入到你的程序中,而共享库,只是在程序中包含了对它们的引用。现在你有了一个完整的程序,随时可以运行。当你从shell中启动它,它就被传递给了加载器。
加载:本步骤发生在你的程序启动时。首先程序需要被扫描以便引用共享库。程序中所有被发现的引用都立即生效,对应的库也被映射到程序。

第3步和第4步就是共享库的奥秘所在。

现在,开始我们一个简单的示例。

foo.h:


代码如下:

#ifndef foo_h__
#define foo_h__
extern void foo(void);
#endif  // foo_h__

foo.c:


代码如下:

#include <stdio.h>

void foo(void)
{
    puts("Hello, I'm a shared library");
}

main.c:


代码如下:

#include <stdio.h>
#include "foo.h"

int main(void)
{
    puts("This is a shared library test...");
    foo();
    return 0;
}

oo.h 定义了一个接口连接我们的库,一个简单的函数,foo()。foo.c包含了这个函数的实现,main.c是一个用到我们库的驱动程序。

为了更好的演示本例子,所有代码都放在/home/username/foo目录下。

Step 1: 编译无约束位代码

我们需要把我们库的源文件编译成无约束位代码。无约束位代码是存储在主内存中的机器码,执行的时候与绝对地址无关。


代码如下:

$ gcc -c -Wall -Werror -fpic foo.c

Step 2: 从一个对象文件创建共享库

现在让我们将对象文件变成共享库。我们将其命名为libfoo.so:


代码如下:

$ gcc -shared -o libfoo.so foo.o

Step 3: 连接共享库

如你所见,一切都很简单。我们现在有了一个共享库。现在我们编译我们的main.c并且将它连接到libfoo。我们将最终的运行程序命名为test。注意:-lfoo选项并不是搜寻foo.o,而是libfoo.so。GCC编译器会假定所有的库都是以lib开头,以.so或.a结尾(.so是指shared object共享对象或者shared libraries共享库,.a是指archive档案,或者静态连接库)。


代码如下:

$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status

告诉GCC去哪找共享库

Uh-oh!连接器不知道该去哪里找到libfoo。GCC有一个默认的搜索列表,但我们的目录并不在那个列表当中。我们需要告诉GCC去哪里找到libfoo.so。这就要用到-L选项。在本例中,我们将使用当前目录/home/username/foo:


代码如下:

$ gcc -L/home/username/foo -Wall -o test main.c -lfoo

Step 4: 运行时使用库

好的,没有异常。让我们运行一下程序:


代码如下:

$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

Oh no! 加载器不能找到共享库。我们没有将它安装到标准位置,因此我们需要帮一帮加载器。我们有两个选项:使用环境变量LD_LIBRARY_PATH或者rpath。让我们先看看LD_LIBRARY_PATH:

使用LD_LIBRARY_PATH


代码如下:

$ echo $LD_LIBRARY_PATH

目前什么都没有。现在把我们的工作目录添加到LD_LIBRARY_PATH中:


代码如下:

$ LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

为什么还报错?虽然我们的目录在LD_LIBRARY_PATH,但是我们还没有导出它。在Linux中,如果你不将修改导出到一个环境变量,这些修改是不会被子进程继承的。加载器和我们的测试程序没有继承我们所做的修改,不过放心,要修复这个问题很简单:


代码如下:

$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
This is a shared library test...
Hello, I'm a shared library

很好,运行正常!LD_LIBRARY_PATH很适合做快速测试,尤其是那些你没有管理员权限的系统。另一方面,导出LD_LIBRARY_PATH变量意味着可能会造成其他依赖LD_LIBRARY_PATH的程序出现问题,因此在做完测试后最好将LD_LIBRARY_PATH恢复成之前的样子。

使用rpath

现在让我们来试试rpath,首先需要清除LD_LIBRARY_PATH,确保我们是使用rpath来搜索库文件。Rpath,或者称为run path,是种可以将共享库位置嵌入程序中的方法,从而不用依赖于默认位置和环境变量。我们在连接环节使用rpath。注意“-Wl,-rpath=/home/username/foo”选项。-Wl会发送以逗号分隔的选项到连接器,因此我们通过它发送-rpath选项到连接器。(译者按:逗号分隔符后面没有空格,而是紧跟需要发送的选项。本例中为-rpath。一定注意"-Wl,-rpath"之间没有空格。)


代码如下:

$ unset LD_LIBRARY_PATH
$ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo
$ ./test
This is a shared library test...
Hello, I'm a shared library

非常好,奏效了。rpath方法非常棒,因为每个程序都可以单独罗列它自己的共享库位置,因此不同的程序不会再在错误的路径上搜索LD_LIBRARY_PATH。

rpath和LD_LIBRARY_PATH

rpath也存在一些反作用面。首先,它要求共享库必须安装在一个固定的位置,这样所有的用户才可以在同一个位置访问到库。这就意味着在系统配置中不够灵活。其次,如果库涉及NFS挂载或者其他网络驱动,你在启动程序时会遇到延时或者更糟的情况。

使用ldconfig修改ld.so

如果我们想让系统上所有用户都可以使用我的库时该怎么办?对此,你需要管理员权限。缘由有二:首先,将库放到标准位置,很可能是/usr/lib或者/usr/local/lib,这些地方普通用户是没有写的权限。其次,你需要修改ld.so配置文件并缓存。以root身份做一下操作:


代码如下:

$ cp /home/username/foo/libfoo.so /usr/lib
$ chmod 0755 /usr/lib/libfoo.so

现在文件在标准位置,对所有人都可读。我们现在需要告诉加载器库文件可用,因此让我们更新一下缓存:


代码如下:

$ ldconfig

这将创建一个链接到我们的共享库,并且更新缓存以便它可立即生效。让我们再核实一下:


代码如下:

$ ldconfig -p | grep foo
libfoo.so (libc6) => /usr/lib/libfoo.so

现在我们的库安装好了,在我们开始测试它之前,我们一定要先清理一下其他东西:

以防万一,先清理一下LD_LIBRARY_PATH:


代码如下:

$ unset LD_LIBRARY_PATH

重新连接我们的可执行程序。注意:我们不需要-L选项,因为我们的库保存在默认位置,我们可以不用rpath选项:


代码如下:

$ gcc -Wall -o test main.c -lfoo

让我们确认一下我们将使用/usr/lib中我们库的实例,使用ldd命令:


代码如下:

$ ldd test | grep foo
libfoo.so => /usr/lib/libfoo.so (0x00a42000)

很好,现在运行一下程序吧:


代码如下:

$ ./test
This is a shared library test...
Hello, I'm a shared library

以上就是所有内容。我们讲述了如何构建一个共享库,如何连接,如果解决最常见的共享库加载问题,还有各种方法的优劣性。

附:

1. Shared Libraries(共享库) 和 Static Libraries(静态库)区别

共享库是以.so(Windows平台为.dll,OS X平台为.dylib)作为后缀的文件。所有和库有关的代码都在这一个文件中,程序在运行时引用它。使用共享库的程序只会引用共享库中它要用到的那段代码。

静态库是以.a(Windows平台为.lib)作为后缀的文件。所有和库有关的代码都在这一个文件中,静态库在编译时就被直接链接到了程序中。使用静态库的程序从静态库拷贝它要使用的代码到自身当中。(Windows还有一种.lib文件是用来引用.dll文件,但其实它们和第一种情况是一样的。)

两种库各有千秋。

使用共享库可以减少程序中重复代码的数量,让程序体积更小。而且让你可以用一个功能相同的对象来替换共享对象,这样可以在增加性能的同时不用重新编译那些使用到该库的程序。但是使用共享库会小额增加函数的执行的成本,同样还会增加运行时的加载成本,因为共享库中的符号需要关联到它们使用的东西上。共享库可以在运行时加载到程序中,这是二进制插件系统最通用的一种实现机制。

静态库总体上增加了程序体积,但它也意味着你无需随时随地都携带一份要用到的库的拷贝。因为代码在编译时就已经被关联在一起,因此在运行时没有额外的消耗。

2. GCC首先在/usr/local/lib搜索库文件,其次在/usr/lib,然后搜索-L参数指定路径,搜索顺序和-L参数给出路径的顺序一致。

3. 默认的GNU加载器ld.so,按以下顺序搜索库文件:

首先搜索程序中DT_RPATH区域,除非还有DT_RUNPATH区域。
其次搜索LD_LIBRARY_PATH。如果程序是setuid/setgid,出于安全考虑会跳过这步。
搜索DT_RUNPATH区域,除非程序是setuid/setgid。
搜索缓存文件/etc/ld/so/cache(停用该步请使用'-z nodeflib'加载器参数)
搜索默认目录/lib,然后/usr/lib(停用该步请使用'-z nodeflib'加载器参数)

(0)

相关推荐

  • 关于安装linux redhat后无法使用yum命令安装gcc-c++问题的解决过程

    初入职场,给linux redhat安装环境的时候,遇到这么个问题 [root@localhost ~]# yum -y install gcc Loaded plugins: katello, product-id, security, subscription-manager Updating certificate-based repositories. Unable to read consumer identity Setting up Install Process No packa

  • 浅谈Linux环境下gcc优化级别

    代码优化可以说是一个非常复杂而又非常重要的问题,以笔者多年的linux c开发经验来说优化通常分为两个方面,一是人为优化,也就是基于编程经验采用更简易的数据结构函数等来降低编译器负担,二是采用系统自带的优化模式,也就是gcc - o系列,下面我将简述一下各级优化的过程以及实现. gcc - o1 首先o1上面还有一个o0,那个是不提供任何优化,项目中几乎不会使用,而o1使用就非常广泛了,o1是最基本的优化,主要对代码的分支,表达式,常量来进行优化,编译器会在较短的时间下将代码变得更加短小,这样体

  • Linux上安装GCC编译器过程

    2004年4月20日最新版本的GCC编译器3.4.0发布了.目前,GCC可以用来编译C/C++.FORTRAN.JAVA.OBJC.ADA等语言的程序,可根据需要选择安装支持的语言.GCC 3.4.0比以前版本更好地支持了C++标准.本文以在Redhat Linux上安装GCC3.4.0为例,介绍了GCC的安装过程. 安装之前,系统中必须要有cc或者gcc等编译器,并且是可用的,或者用环境变量CC指定系统上的编译器.如果系统上没有编译器,不能安装源代码形式的GCC 3.4.0.如果是这种情况,可

  • Linux系统中安装gcc和kernel-devel的方法

    gcc:liunx环境中的c/c++编译器,安装软件需要它 kernel-devel:linux内核,安装软件时需要编译内核,故需要保持内核版本一致性 1.Linux操作系统安装好以后,查看gcc是否已经安装: # gcc -v //如果后面出现一大段,则已经安装: 2.查看内核是否一致: #uname -r #rpm -q kernel-devel 如果两个命令得到的版本号一致,那么恭喜你可以直接安装Vmware Tools了:不过一般情况下,两个版本是不一致的,或者是提示kernel-dev

  • Linux中 CentOS 6.5 手动升级gcc到gcc-6.1.0

    从进入码农时代已经有好几年了,一直没有写博文的习惯,用到的很多东西没有记录,到后面就忘了,需要用时,又不停上百度google一下,费时间又费精力,还不一定能找到满意的答案,因此开个博客记录下.(废话不多说了) 今天在公司服务器上源码安装Nodejs报错 WARNING: C++ compiler too old, need g++ 4.8 or clang++ 3.4 (CXX=g++),直接yum update gcc不能升级到4.8,然后只能手动处理了. 开始,如果linux安装了wget命

  • linux使用gcc编译c语言共享库步骤

    对任何程序员来说库都是必不可少的.所谓的库是指已经编译好的供你使用的代码.它们常常提供一些通用功能,例如链表和二叉树可以用来保存任何数据,或者是一个特定的功能例如一个数据库服务器的接口,就像MySQL. 大部分大型的软件项目都会包含若干组件,其中一些你发现可以用在其他项目中,又或者你仅仅出于组织目的将不同组件分离出来.当你有一套可复用的并且逻辑清晰的函数时,将其构建为一个库会十分有用,这样你就不将这些源代码拷贝到你的源代码中,而且每次都要再次编译它们.除此之外,你还可以保证你的程序各模块隔离,这

  • Linux下g++编译与使用静态库和动态库的方法

    在windows环境下,我们通常在IDE如VS的工程中开发C++项目,对于生成和使用静态库(*.lib)与动态库(*.dll)可能都已经比较熟悉,但是,在linux环境下,则是另一套模式,对应的静态库(*.a)与动态库(*.so)的生成与使用方式是不同的.刚开始可能会不适应,但是用多了应该会习惯这种使用,因为步骤上并没有VS下配置那么繁琐. 下面就分别总结下linux下生成并使用静态库与动态库的方法:(由于是C++项目,所以编译器用的g++,但是与gcc的使用是相通的) 首先是准备工作,把我们需

  • 浅谈Linux C语言动态库及静态库

    假设在math目录下已编辑好add.c sub.c div.c mul.c func_point.c文件,func_point.c为包含main()的源文件! 动态库的制作: 方法一: gcc -c -fPIC add.c sub.c div.c mul.c //-c表示生成.o目标文件,-f后加一些编译选项,PIC表示与位置无关 gcc -shared -o libmymath.so add.o sub.o mul.o div.o//创建共享库mymath,添加add.o,sub.o,mul.

  • Python调用C语言开发的共享库方法实例

    在helloworld工程中,编写了一个简单的两个数值相加的程序,编译成为共享库后,如何使用python对其进行调用呢? 使用ll命令列出当前目录下的共享库,其中共享库名为libhelloworld.so.0.0.0 复制代码 代码如下: ufo@ufo:~/helloworld/.libs$ ll 总用量 32 drwxr-xr-x 2 ufo ufo 4096  1月 29 14:54 ./ drwxr-xr-x 6 ufo ufo 4096  1月 29 16:08 ../ -rw-r--

  • GCC 编译使用动态链接库和静态链接库的方法

    1 库的分类 根据链接时期的不同,库又有静态库和动态库之分. 静态库是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行. 有别于静态库,动态库的链接是在程序执行的时候被链接的.所以,即使程序编译完,库仍须保留在系统上,以供程序运行时调用.(TODO:链接动态库时链接阶段到底做了什么) 2 静态库和动态库的比较 链接静态库其实从某种意义上来说也是一种粘贴复制,只不过它操作的对象是目标代码而不是源码而已.因为静态库被链接后库

  • go语言静态库的编译和使用方法

    本文主要介绍go语言静态库的编译和使用方法,以windows平台为例,linux平台步骤一样,具体环境如下: >echo %GOPATH% E:\share\git\go_practice\ >echo %GOROOT% C:\Go\ >tree /F %GOPATH%\src 卷 work 的文件夹 PATH 列表 卷序列号为 0009-D8C8 E:\SHARE\GIT\GO_PRACTICE\SRC │ main.go │ └─demo demo.go 在%GOPATH%\src目

  • Linux环境g++编译GDAL动态库操作方法

    一.编译步骤 解压下载的GDAL源程序,并在命令行中切换到解压目录. tar -xzvf gdal-2.1.3.tar.gz cd gdal-2.1.3 GDAL可通过configure来实现一些自定义配置,可通过./configure –h命令来查看.--prefix=path表示设置GDAL的make install后的build目录,里面有生成的头文件和动态库.输入如下命令: ./configure --prefix=/root/Test/gdalbuild 这时可以发现目录中新生成了GD

  • 老生常谈C语言静态函数库的制作和使用

    C语言的函数库是多组经过验证的常用函数的集合,编写C语言程序时使用库函数,既可以提高程序运行效率,又可以提高编程质量,使用方法如#include 和#include. 根据使用库函数时,函数库加载时机的差异,将函数库分为静态函数库和动态函数库,具体差异是:C语言程序如果使用静态函数库的函数,那么整个函数库的代码都会和C语言程序一起编译成可执行代码,程序的体积会膨胀:如果使用动态函数库的函数,则C语言程序只会和函数库文件名以及函数名一起编译成可执行代码(不编译函数代码),运行时去查找函数库文件和函

  • 深入分析Linux下如何对C语言进行编程

    1.源程序的编译    在Linux下面,如果要编译一个C语言源程序,我们要使用GNU的gcc编译器. 下面我们以一个实例来说明如何使用gcc编译器. 假设我们有下面一个非常简单的源程序(hello.c):  int main(int argc,char **argv)  {printf("Hello Linux/n");  } 要编译这个程序,我们只要在命令行下执行: gcc -o hello hello.c gcc 编译器就会为我们生成一个hello的可执行文件.执行./hello

  • GCC 编译c程序的方法及过程解析

    目前 Linux 下最常用的 C 语言编译器是 GCC ( GNU Compiler Collection ),它是 GNU 项目中符合 ANSI C 标准的编译系统,能够编译用 C . C++ 和 Object C 等语言编写的程序. GCC 不仅功能非常强大,结构也异常灵活.最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java . Fortran . Pascal . Modula-3 和 Ada 等.开放.自由和灵活是 Linux 的魅力所在,而这一点在 GCC 上的体现

随机推荐