35.静态链接库和动态链接库



35.1.函数库的前世今生

(1)函数库就是一些事先写好的函数的集合,因为函数是模块化的,因此可以被复用;我们写好了某个函数,可以被反复使用,譬如A写好了某个函数然后共享出来,当B有相同的需求时就不需自己写直接用A写好的这个函数即可。

(2)最开始是没有函数库的,每个人写程序都要从零开始自己写,时间长了慢慢的早期的程序员就积累下来了一些有用的函数;早期的程序员经常参加行业聚会,在聚会上大家互相交换各自的函数库;后来程序员中的一些大神就提出把大家各自的函数库收拢在一起,然后经过校准和整理,最后形成了一份标准化的函数库,就是现在的标准的函数库,譬如说glibc。

(3)早期的函数共享都是以源代码的形式进行的,该方式共享是最彻底的(后来这种源码共享的方向就形成了我们现在的开源社区),但该方式的缺点就是无法以商业化形式来发布函数库;商业公司需要将自己的有用的函数库共享给别人(当然是付费的),但又不能给客户源代码,则解决方案就是以库(静态库和动态库)的形式来提供。



35.2.静态库和动态库

(1)较早出现的是静态链接库,静态库其实就是商业公司将自己的函数库源代码经过只编译不链接形成.o的目标文件,然后用ar工具将.o文件归档成.a的归档文件(.a的归档文件又叫静态链接库文件);商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用;客户拿到.a和.h文件后,通过.h头文件得知库中的库函数的原型,然后在自己的.c文件中直接调用这些库文件,在链接的时候链接器会去.a文件中拿出被调用的函数的编译后的.o二进制代码段链接进去形成最终的可执行程序;静态库在用户链接自己的可执行程序时就已经把调用的库中的函数的代码段链接进最终可执行程序中了,该方式的优点是可以独立执行,缺点是程序太过庞大,尤其是有多个应用程序都使用了该库函数时,实际上在多个应用程序最后生成的可执行程序中都各自有1份该库函数的代码段,当这些应用程序同时在内存中运行时,实际上在内存中有多个该库函数的代码段,导致重复加载。

(2)动态链接库出现较晚,效率更高一些,现在我们一般都是使用动态库;动态链接库本身不将库函数的代码段链接入可执行程序,只是做个标记,然后当应用程序在内存中执行时,运行时环境发现它调用了某个动态库中的库函数时,会去加载这个动态库到内存中,以后不管有多少个应用程序去调用该库中的函数都会跳转到第1次加载的地方去执行,不会重复加载。

(3)gcc中编译链接程序默认是使用动态库的,要想使用静态链接需要显式用-static来强制静态链接;调用库函数时应包含相应的头文件;调用库函数时需注意函数原型;有些库函数链接时需要额外用-lxxx来指定链接(譬如数学库和线程库);如果使用动态库要注意-L指定动态库的地址。



35.3.字符串库函数

(1)字符串就是由多个字符在内存中连续分布组成的字符结构,字符串的特点是指定了开头(字符串的指针)和结尾(结尾固定为字符’\0’),而没有指定长度(长度由开头地址和结尾地址相减得到)。

(2)因为字符串处理的需求是客观的,所以从很早开始人们就在写很多关于字符串处理的函数,然后逐渐形成了现在的字符串处理函数库;面试笔试时,常用字符串处理函数也是经常考到的点。

(3)C库中字符串处理函数包含在string.h中,该文件在ubuntu系统位于/usr/include目录中,在学习字符串处理函数时可参照string.h文件和man手册及网上的博客针对常用的字符串口处理函数进行针对性的学习和总结,这样能在很大程度上提高编程能力。

(4)常见字符串处理函数memcpy(确定src和dst不会overlap,则使用memcpy效率高)、memmove(确定会overlap或者不确定但是有可能overlap,则使用memove比较保险)、memset、memcmp、memchr、strcpy、strncpy、strcat、strncat、strcmp、strncmp、strdup、strndup、strchr、strstr、strtok。



35.4.数学库函数

(1)使用数学库函数的时候,只需要包含math.h即可;真正的数学运算的函数定义在/usr/include/i386-linux-gnu/bits/mathcalls.h中。

(2)计算开平方时使用库函数double sqrt(double x);在编译后需注意区分编译时警告/错误及链接时的错误;4.6.10.math.c:9:13: warning: incompatible implicit declaration of built-in function ‘sqrt’ [enabled by default](编译时警告/错误);double b = sqrt(a);;4.6.10.math.c:(.text+0x1b): undefined reference to `sqrt’ collect2: error: ld returned 1 exit status(链接时错误,sqrt函数有声明(声明就在math.h中)有引用(在math.c)但是没有定义,链接器找不到函数体;sqrt本来是库函数,在编译器库中是有.a和.so链接库的(函数体在链接库中的))。

(3)C链接器的工作特点,因为库函数有很多,链接器去库函数目录搜索的时间比较久,为了提升速度采用了折中的方案;链接器只是默认的寻找几个最常用的库,如果是一些不常用的库中的函数被调用,需要程序员在链接时明确给出要扩展查找的库的名字;链接时可以用-lxxx来指示链接器去到libxxx.so中去查找这个函数。

(4)链接时加-lm即告诉链接器到libm中去查找用到的函数;实战中发现在高版本的gcc中,经常会出现没加-lm也可以编译链接的;我们可使用ldd命令查看可执行程序链接使用到的库的路径(ldd a.out)。



35.5.自己制作静态链接库并使用

(1)自己制作静态链接库;首先使用gcc -c只编译不连接(gcc rston.c -o rston.o -c),生成.o文件;然后使用ar工具打包成.a归档文件(ar -rc librston.a rston.o);库名不能随便乱起,一般是lib+库名称(librston);后缀名是.a表示是1个归档文件(librston.a);制作出静态库之后,发布时需要发布.a文件和.h文件。

(2)使用静态链接库;把.a和.h都放在某个目录中,然后在.c文件中包含库的.h,然后直接使用库函数;第1次编译方法(gcc testlib.c -o testlib)+报错信息testlib.c:(.text+0x19): undefined reference to `add’+collect2: ld returned 1 exit status;第2次编译方法(gcc testlib.c -o testlib -lrston)+报错信息/usr/bin/ld: cannot find -lrston+collect2: ld returned 1 exit status;第3次编译方法(gcc testlib.c -o testlib -lrston -L.)+无报错,生成testlib,执行正确。

(3)ar命令和nm命令,除了ar归档命令外,还有个nm命令也很有用,它可以用来查看某个.a文件中都有哪些符号。



35.6.自己制作动态链接库并使用

(1)自己制作动态链接库动;动态链接库的后缀名是.so(对应windows系统中的dll),静态链接库的扩展名是.a;首先使用gcc -c只编译不连接(gcc rston.c -o rston.o -c -FPIC;-fPIC是位置无关码),生成.o文件;然后使用gcc工具打包成.so文件(gcc rston.o -o librston.so -shared;-shared是按照共享库的方式来链接);制作成动态库后,发布时需发布.so文件和.h文件。

(2)使用动态链接库;第1次编译方法(gcc testlib.c -o testlib)+报错信息testlib.c:(.text+0x19): undefined reference to `add’+collect2: ld returned 1 exit status;第2次编译方法(gcc testlib.c -o testlib -lrston)+报错信息/usr/bin/ld: cannot find -lrston+collect2: ld returned 1 exit status;第3次编译方法(gcc testlib.c -o testlib -lrston -L.)+无报错,生成testlib,执行错误。

(3)执行报错信息(error while loading shared libraries: librston.so: cannot open shared object file: No such file or directory);错误原因是动态链接库运行时需要被加载(运行时环境在执行testlib程序的时候发现其动态链接了librston.so,于是乎会去固定目录尝试加载libaston.so,如果加载失败则会打印以上错误信息)。

(4)解决方法1是将librston.so放到固定目录下即可,该固定目录一般是/usr/lib目录(sudo cp librston.so /usr/lib);解决方法2是将libaston.so所在的目录导出到环境变量LD_LIBRARY_PATH中,操作系统在加载固定目录/usr/lib之前,会先去LD_LIBRARY_PATH这个环境变量所指定的目录下去寻找,如果找到就不用去/usr/lib下面找了,如果没找到再去/usr/lib下面找(export LD_LIBRARY_PATH=dD_LIBRARY_PATH:d(pwd)注意此为shell中运行该命令;若在Makefile中则需运行命令export LD_LIBRARY_PATH=dD_LIBRARY_PATH:d(shell pwd) && ./testlib);解决方法3是使用ldconfig(仅限于ubuntu中)。

(5)ldd命令的作用是可以在某个使用了共享库(动态链接库)的程序执行之前解析出该程序使用了哪些共享库,并且查看这些共享库是否能被找到并解析,即使用该命令可提前知道该程序能否正确执行。


35.library
/*
 * 公司:XXXX
 * 作者:Rston
 * 项目:静态链接库和动态链接库
 * 功能:演示静态库和动态库所占用空间大小对比。
 */
#include <stdio.h>

int main(int argc, char **argv)
{
    // 动态库->gcc 35.library.c->ls -l a.out(大小:7164)
    // 静态库->gcc 35.library.c -static->ls -l a.out(大小:751243)
    printf("hello world.\n");

    return 0;
}

35.mem_cpy_move_ccpy
/*
 * 公司:XXXX
 * 作者:Rston
 * 项目:静态链接库和动态链接库
 * 功能:演示memcoy和memmove和memccoy函数的用法和区别。
 */
#include <stdio.h>
#include <string.h>
//#define NDEBUG
#include <assert.h>

// linux下memcpy源码
// void *memcpy(void *dest, const void *src, size_t n);
// 从源src所指的内存地址的起始位置开始拷贝n个字节
// 到目标dest所指的内存地址的起始位置中,返回值为指向dest的指针
void *memcpy1(void *dest, const void *src, size_t n)
{
    assert((src != NULL) && (dest != NULL));        // 判断参数合法性
    void *rev = dest;
    while (n--)
    {
        *(char *)dest = *(char *)src;
        dest = (char *)dest + 1;                    // 单个字节进行copy
        src = (char *)src + 1;
    }
    return rev;                                     // 返回值为指向dest的指针
}

// linux下memmove源码
// void *memmove(void *dest, const void *src, size_t n);
// 从源src所指的内存地址的起始位置开始拷贝n个字节
// 到目标dest所指的内存地址的起始位置中,返回值为指向dest的指针
void *memmove1(void *dest, const void *src, size_t n)
{
    assert((src != NULL) && (dest != NULL));        // 判断参数合法性
    void *rev = dest;
    // 原则:未拷贝的src数据与已拷贝的dest数据不能被改变
    if ((src >= dest) || ((char *)dest >= ((char *)src + n)))
    {// 若dst和src区域没有重叠,则从起始处开始逐一拷贝
        while (n--)
        {
            *(char *)dest = *(char *)src;
            dest = (char *)dest + 1;                // 正向拷贝
            src = (char *)src + 1;
        }
    }
    else
    {// 若dst和src 区域交叉,则从尾部开始向起始位置拷贝,这样可以避免数据冲突
        dest = (char *)dest + (n - 1);
        src = (char *)src + (n - 1);
        while (n--)
        {
            *(char *)dest = *(char *)src;
            dest = (char *)dest - 1;                // 反向拷贝
            src = (char *)src - 1;
        }
    }
    return rev;                                     // 返回值为指向dest的指针
}

// linux下memmccpy源码
// void *memccpy(void *dest, const void *src, int c, size_t n);
// 由src所指内存区域复制不多于n个字节到dest所指内存区域,如果遇到字符ch则停止复制。
// 返回指向dest中值为c的下一个字节的指针,如果src前n个字节中不存在c则返回NULL。c被复制。
void *memccpy1(void *dest, const void *src, int c, size_t n)
{
    assert((src != NULL) && (dest != NULL));        // 判断参数合法性
    while (n)
    {
        *(char *)dest = *(char *)src;
        dest = (char *)dest + 1;
        if (*(char *)src == (char)c)                // 若发现指定的字符,则停止拷贝
        {
            break;
        }
        src = (char *)src + 1;
        n--;
    }
    return (n ? dest : NULL);                       // 返回指向字符’c’后的第一个字符的指针;
                                                    // 否则返回NULL,c被复制
}

// 函数关键点:
// (1)以上函数不关心被复制的数据类型(因为函数的参数类型是void*(未定义类型指针)),
// 只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。
// (2)dest指针要分配足够的空间,也即大于等于n字节的空间,
// 如果没有分配空间,会出现段错误。
// (3)若能确定dest和src所指的内存空间不能重叠,则使用memcpy或memccpy,
// 如果发生了重叠,使用memmove会更加安全。
// (4)如果目标destin本身已有数据,执行memcpy后,将覆盖原有数据(最多覆盖n)。
// 如果要追加数据,则每次执行memcpy后,要将目标数组地址增加到你要追加数据的地址。
// (5)memccpy增加了指定字符判断功能,使用时注意添加dest结束标志‘\0’,
// 但是没有考虑内存空间重叠问题。

// 备注:
// (1)如果通过长期的严格测试,能够保证使用者不会使用零地址作为参数调用函数,
// 则希望有简单的方法关掉参数合法性检查,可加#define NDEBUG取消assert()。
// (2)size_t是标准C库中定义的数据类型,32位系统中为unsigned int,
// 在64位系统中为long unsigned int,估计stdio.h中有相应的typedef语句

int main(void)
{
    char src1[] = "hello world";        // 12字节
    char src2[1024] = "hello world";
    char src3[1024] = "hello world";
    char dest1[sizeof(src1)] = {0};
    char dest2[sizeof(src1)] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
    char dest3[20] = {0};
    char c;
    char *rev;

    // sizeof(src1)=12.strlen(src1)=11.
    printf("sizeof(src1)=%d.strlen(src1)=%d.\n", sizeof(src1), strlen(src1)); 

#if 0
    // 测试memcpy字符串的‘/0‘问题
    memcpy1(dest1, src1, sizeof(src1));
    printf("after memcpy dest1 is:%s.\n", dest1);       // after memcpy dest1 is:hello world.
    memcpy1(dest2, src1, strlen(src1));
    printf("after memcpy dest2 is:%s.\n", dest2);       // after memcpy dest2 is:hello world.
#endif

#if 0
    // 测试memcpy参数合法性检验
    memcpy1(dest1, NULL, sizeof(src1));                 // a.out: memcpy.c:67: memcpy1: Assertion
    memcpy1(NULL, src1, sizeof(src1));                  // `(src != ((void *)0)) && (dest != ((void *)0))‘ failed.
#endif                                                  // Aborted (core dumped)

#if 0
    // 测试memcpy复制任意连续的字节长度(长度小于等于n),复制wor
    memcpy1(dest1, (src1 + 6), 3);
    printf("after copy dest1 is %s.\n", dest1);         // after copy dest1 is wor.
#endif

#if 0
    // 测试内存重叠对memcpy和memmove的影响
    printf("before memcopy src2 is %s.\n", src2);       // before memcopy src2 is hello world.
    memcpy1((src2 + 6), src2, strlen(src2));
    printf("after memcopy src2 is %s.\n", src2);        // after memcopy src2 is hello hello hello.

    printf("before memmove dest is %s.\n", src3);       // before memmove dest is hello world.
    memmove1((src3 + 6), src3, strlen(src3));
    printf("after memmove dest is %s.\n", src3);        // after memmove dest is hello hello world.
#endif

#if 0
    // 测试memcppy找到/没找到相应字符的情况
    c = ‘z‘;
    rev = memccpy1(dest3, src2, c, strlen(src2));
    if (NULL == rev)
    {
        printf("can not find character %c.\n", c);      // can not find character z.
        printf("after memccpy dest3 is %s.\n", dest3);  // after memccpy dest3 is hello world.
    }
    else
    {
        printf("find character %c success.\n", c);      // find character r success.
        *rev = ‘\0‘;
        printf("after memccpy dest3 is %s.\n", dest3);  // after memccpy dest3 is hello wor.
    }
#endif
    return 0;
}

35.sqrt
/*
 * 公司:XXXX
 * 作者:Rston
 * 项目:静态链接库和动态链接库
 * 功能:演示调用某个数学库函数。
 */
#include <stdio.h>
#include <math.h>

// 编译时需加上-lm链接命令“gcc 35.sqrt.c -lm”
// 执行“ldd a.out”命令后输出
// linux-gate.so.1 =>  (0xb7727000)
// libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb76e7000)
// libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb753d000)
// /lib/ld-linux.so.2 (0xb7728000)

int main(int argc, char **argv)
{
    double a = 16.0;
    // sqrt(16.000000) = 4.000000.
    printf("sqrt(%lf) = %lf.\n", a, sqrt(a));

    return 0;
}

35.archive_static/lib_create/
rston.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 项目:静态链接库和动态链接库
 * 功能:自己制作静态链接库。
 */
#include <stdio.h>

// 简单的加法函数
int add(int a, int b)
{
    printf("func add in rston.c.\n");
    return (a + b);
}
*********
rston.h
#ifndef _RSTON_H_
#define _RSTON_H_

int add(int a, int b);

#endif
*********
Makefile
all:
    gcc rston.c -o rston.o -c
    ar -rc librston.a rston.o
    nm librston.a

35.archive_static/lib_use/
testlib.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 项目:静态链接库和动态链接库
 * 功能:自己使用静态链接库。
 */
#include "rston.h"
#include <stdio.h>

int main(int argc, char **argv)
{
    printf("add(7, 8) = %d.\n", add(7, 8));

    return 0;
}
*********
rston.h
#ifndef _RSTON_H_
#define _RSTON_H_

int add(int a, int b);

#endif
*********
Makefile
all:
    gcc testlib.c -o testlib -lrston -L.
    nm librston.a 

35.shared_dynamic/lib_create/
rston.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 项目:静态链接库和动态链接库
 * 功能:自己制作动态链接库。
 */
#include <stdio.h>

// 简单的加法函数
int add(int a, int b)
{
    printf("func add in rston.c.\n");
    return (a + b);
}
*********
rston.h
#ifndef _RSTON_H_
#define _RSTON_H_

int add(int a, int b);

#endif
*********
Makefile
all:
    gcc rston.c -o rston.o -c -FPIC
    gcc rston.o -o librston.so  -shared
    nm librston.so

35.shared_dynamic/lib_use/
testlib.c
/*
 * 公司:XXXX
 * 作者:Rston
 * 项目:静态链接库和动态链接库
 * 功能:自己使用动态链接库。
 */
#include "rston.h"
#include <stdio.h>

int main(int argc, char **argv)
{
    printf("add(7, 8) = %d.\n", add(7, 8));

    return 0;
}
*********
rston.h
#ifndef _RSTON_H_
#define _RSTON_H_

int add(int a, int b);

#endif
*********
Makefile
all:
    gcc testlib.c -o testlib -lrston -L.

    #方法1使用动态链接库
    #sudo cp librston.so /usr/lib/
    #sudo rm -f /usr/lib/librston.so 

    #方法2使用动态链接库
    #注意Makefile中的export和shell中的export作用不同
    #参考链接http://blog.csdn.net/sdustliyang/article/details/6959715
    #export LD_LIBRARY_PATH=$D_LIBRARY_PATH:$(shell pwd) && ./testlib
    #export LD_LIBRARY_PATH= && ./testlib

    ldd testlib

时间: 2024-10-12 15:12:29

35.静态链接库和动态链接库的相关文章

[C] linux静态链接库与动态链接库详解

http://blog.chinaunix.net/u2/76292/showart.php?id=1274181 一顺便说说了哦  通常情况下,对函数库的链接是放在编译时期(compile time)完成的.所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file).程序在运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下.所以这些函数库被成为静态库(static libaray),通常文件名为"libxx

Linux下 静态链接库 和 动态链接库

先来说说C/C++编译过程 编译: 检查语句符号定义,将C/C++代码翻译生成中间语言. 链接: 将中间代码整合,生成可执行的二进制代码. 简单的说,库文件都是一种特殊的中间语言文件,静态库还是一种特殊格式的归档文件(打包的文件). 使用静态库: 1. 先编写库函数 1 #ifndef _PRINT_TEST_H_ 2 3 #define _PRINT_TEST_H_ 4 #ifdef __cplusplus 5 extern "C" 6 { 7 #endif 8 9 extern i

静态链接库与动态链接库

静态链接库与动态链接库都是共享代码的方式. 静态链接库(lib): 在程序执行之前完成所有的组装工作,生成一个可执行的目标文件(EXE文件). 静态库的两个特点: 链接后产生的可执行文件包含了所有需要调用的函数的代码,因此占用磁盘空间较大. 如果有多个(调用相同库函数的)进程在内存中同时运行,内存中就存有多份相同的库函数代码,因此占用内存空间较多. 动态链接库(dll&lib): 在程序装载内存的时候才真正的把库函数代码链接进行确定它们的地址,并且就算有几个程序同时运行,内存也只存在一份函数代码

C语言之静态链接库和动态链接库

1:静态链接库 比较早出现的是静态链接库.静态库其实就是商业公司将自己的函数库源代码经过只编译不连接形成.o的目标文件,然后用ar工具将.o文件归档成.a的归档文件(.a的归档文件又叫静态链接库文件).商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用:客户拿到.a和.h文件后,通过.h头文件得知库中的库函数的原型,然后在自己的.c文件中直接调用这些库文件,在连接的时候链接器会去.a文件中拿出被调用的那个函数的编译后的.o二进制代码段链接进去形成最终的可执行程序. 2:动态链接库 动态

静态链接库和动态链接库的区别及优缺点

动态链接库和静态链接库的区别 本文参考了以下博客:      1. http://blog.csdn.net/gamecreating/article/details/5504152      2. http://blog.csdn.net/left_la/article/details/12098545      3. http://blog.csdn.net/augusdi/article/details/6460415 静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序

介绍静态链接库和动态链接库的区别,及在VC++6.0中的建立和使用

首先介绍一下链接库:链接库分为动态链接库和静态链接库两种 LIB是静态链接库,在程序编译连接的时候是静态链接,其对应的文件格式是.lib.即当程序采用静态链接库的时候,.lib文件中的函数被链接到最终的可执行文件中,因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行. DLL是动态链接库,在程序运行的时候被调用,其对应的文件的格式是.dll.即当程序采用动态链接的时候,.dll文件中的函数并没有被链接到可执行文件中,可执行文件只是保存了函数的地址信息.但是

[C++] 静态链接库和动态链接库的区别

静态链接库和动态链接库的区别 一.静态链接库 预编译->编译->汇编->链接 Linux: 生成目标文件 g++ -c source.cpp -o source.o 打包成静态链接库 ar -crv source.a source.o 使用静态链接库 g++ test.cpp -L静态链接库目录 -l静态链接库名称没有后缀 二.动态链接库 使用动态链接库是为了规避静态链接库的两个问题. 一个是多个副本的问题,对于静态库都是在编译时刻将其编译到源代码当中,在运行时刻不会再和静态库有任何关系

简单程序的编译链接三种方法(编译多个源文件,静态链接库、动态链接库)

一个程序简单的程序如下: 1 hello.h #ifndef HELLO_H#define HELLO_H void hello(const char *name); #endif 2 hello.c #include <stdio.h>#include <stdlib.h> void hello(const char *name){ printf("hello %s\n",name);} 3 main.c #include <stdio.h>#in

linux静态链接库与动态链接库详解

转:http://bbs.chinaunix.net/thread-1281954-1-1.html 二动态链接库的特点与优势  首先让我们来看一下,把库函数推迟到程序运行时期载入的好处:  1.可以实现进程之间的资源共享.   什么概念呢?就是说,某个程序的在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了.如果有,则让其共享那一个拷贝:只有没有才链接载入.这样的模式虽然会带来一些“动态链接”额外的开销,却大大的节省了系统的内存资源