精华内容
下载资源
问答
  • 根据链接时期的不同,库又有静态库和动态库之分,有别于静态库,动态库的链接是在程序执行的时候被链接
  • 静态链接库的使用需要库的...静态链接库与动态链接库都是共享代码的方式,静态链接库和动态链接库的区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
  • vs-dll-lib动态静态链接库使用项目-包含vs项目配置文件直接编译使用即可学会 这是一个完整的vs项目,一个解决方案,包含5个小项目。包含项目的全部配置文件,直接下载编译,轻松学会使用动态链接库dll与静态链接库...
  • 本文详细介绍了静态链接库与动态链接库的区别,适合于那些对二者概念分不清楚的同学,以及如何创建一个静态库和动态库的方法
  • OpenSSL 在windows10环境下用Visual Studio 2017编译成功的链接库,包括32/64位的动/静态链接库,共四个。
  • 程序运的动态链接和静态链接的区别,可以看下。。
  • openssl-1.0.2l 的静态链接库 libeay32.lib ssleay32.lib,用他编译无需dll就可以运行。 64位
  • 深入浅出静态链接和动态链接

    万次阅读 多人点赞 2018-05-06 09:24:48
    作为一名C/C++程序员,对于编译链接的过程要了然于胸。首先大概介绍一下,编译分为3步,首先对源文件进行预处理,这个过程主要是处理一些#号定义的命令或语句(如宏、#include、预编译指令#ifdef等),生成*.i文件;...

            作为一名C/C++程序员,对于编译链接的过程要了然于胸。首先大概介绍一下,编译分为3步,首先对源文件进行预处理,这个过程主要是处理一些#号定义的命令或语句(如宏、#include、预编译指令#ifdef等),生成*.i文件;然后进行编译,这个过程主要是进行词法分析、语法分析和语义分析等,生成*.s的汇编文件;最后进行汇编,这个过程比较简单,就是将对应的汇编指令翻译成机器指令,生成可重定位的二进制目标文件。以上就是编译的过程,下面主要介绍两种链接方式--静态链接和动态链接。

            静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时,下面来详细介绍这两种链接方式。

    一、静态链接

    1.为什么要进行静态链接

            在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个*.c文件会形成一个*.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接

    2.静态链接的原理

             由很多目标文件进行链接形成的是静态库,反之静态库也可以简单地看成是一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件,如下图,使用ar命令的-a参数查看静态库的组成:


            这里的*.o目标文件在我前面的博客《从编写源代码到程序在内存中运行的全过程解析》中已经讲的很清楚了,不清楚的可以看一下。

            以下面这个图来简单说明一下从静态链接到可执行文件的过程,根据在源文件中包含的头文件和程序中使用到的库函数,如stdio.h中定义的printf()函数,在libc.a中找到目标文件printf.o(这里暂且不考虑printf()函数的依赖关系),然后将这个目标文件和我们hello.o这个文件进行链接形成我们的可执行文件。


            这里有一个小问题,就是从上面的图中可以看到静态运行库里面的一个目标文件只包含一个函数,如libc.a里面的printf.o只有printf()函数,strlen.o里面只有strlen()函数。

            我们知道,链接器在链接静态链接库的时候是以目标文件为单位的。比如我们引用了静态库中的printf()函数,那么链接器就会把库中包含printf()函数的那个目标文件链接进来,如果很多函数都放在一个目标文件中,很可能很多没用的函数都被一起链接进了输出结果中。由于运行库有成百上千个函数,数量非常庞大,每个函数独立地放在一个目标文件中可以尽量减少空间的浪费,那些没有被用到的目标文件就不要链接到最终的输出文件中。

    3.静态链接的优缺点

            静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。

    问题:

    二、动态链接
    1.为什么会出现动态链接

            动态链接出现的原因就是为了解决静态链接中提到的两个问题,一方面是空间浪费,另外一方面是更新困难。下面介绍一下如何解决这两个问题。

    2.动态链接的原理

            动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。下面简单介绍动态链接的过程:

            假设现在有两个程序program1.o和program2.o,这两者共用同一个库lib.o,假设首先运行程序program1,系统首先加载program1.o,当系统发现program1.o中用到了lib.o,即program1.o依赖于lib.o,那么系统接着加载lib.o,如果program1.o和lib.o还依赖于其他目标文件,则依次全部加载到内存中。当program2运行时,同样的加载program2.o,然后发现program2.o依赖于lib.o,但是此时lib.o已经存在于内存中,这个时候就不再进行重新加载,而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中,从而进行链接(这个链接过程和静态链接类似)形成可执行程序。

    3.动态链接的优缺点

            动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。

            据估算,动态链接和静态链接相比,性能损失大约在5%以下。经过实践证明,这点性能损失用来换区程序在空间上的节省和程序构建和升级时的灵活性是值得的。

    4.动态链接地址是如何重定位的呢?

            前面我们讲过静态链接时地址的重定位,那我们现在就在想动态链接的地址又是如何重定位的呢?虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。

    展开全文
  • C++ 动态链接库和静态链接

    千次阅读 多人点赞 2019-09-23 15:59:58
      今天对C++生成动态链接路和静态链接库非常感兴趣,必须搞定,否则都没有心情干其他事了。Let’s go~ 文章目录源程序编译链接生成文件格式预编译编译和优化编译优化生成目标文件链接什么是库?动态静态的区别...

      今天对C++生成动态链接路和静态链接库非常感兴趣,必须搞定,否则都没有心情干其他事了。Let’s go~


    源程序编译链接生成文件格式

      首先贴出从源程序生成可执行文件的过程。
    源程序(source code)->预处理(preprocessor) ->编译和优化 (compiler) - >生成目标文件(object code) ->链接(Linker)->可执行文件
    生成文件个是对应表

      源程序预处理编译和优化生成目标文件链接可执行文件
    文件后缀.h、.cpp、.c.i.s.o.out.exe
    语言c\c++c\c++汇编二进制二进制二进制

    下面是实际举例说明:
    1.h

    #pragma once
    
    void print();  //我是1.h的注释
    

    1.cpp

    #include "1.h"
    #include<iostream>
    //我是1.cpp的注释
    void print()
    {
    	std::cout << "print函数运行成功" << std::endl;
    }
    

    main.cpp

    #include <iostream>
    #include "1.h"
    #define AA "this is a macro!"
    
    int main()
    {
    #ifdef AA  //我是main.cpp的注释
    	print();
    	std::cout << AA << std::endl;
    #endif
    
    	int a = 1;
    	int b = 5;
    	int c = a + b;
    	std::cout << c << std::endl;
    
    	system("pause");
    	return 0;
    }
    

    预编译

    预编译阶段先介绍编译器。先介绍几个概念:GNU、GCC、gcc、g++
    按照概念范围大小排序如下:
    GNU:GNU是一个操作系统。1971年贝尔实验室汤姆森和里奇完成了unix的的基本工作,1973年写成了C语言版本,但是unix并不是贝尔实验室的正式项目,所以也没有商业应用,这两个人就用写论文发表,在学术圈技术交流,高校教授觉得很好,学生也学习,汤姆森和里奇都是免费拷贝分享,一起发展修改进步,结果AT&T公司觉得很有前景,于是选择申请专利商业闭源,这样学生就得花钱买,1985年,Richard Stallman 愤怒的认为unix大家都有过添砖加瓦的建设,应该共享。随即,发起GNU(GNU is not unix)自由操作系统,软件共享运动。本来GNU就是要做一个完整的操作系统来取代unix,但是当编译器、编辑器等其他的外围都写完后,操作系统的内核Hurd一直没有搞定,1991年一个叫Linus Torvalds的学生写了自己的linux,也就是操作系统内核,于是Linux和GNU组合起来成了现在的GNU/Linux操作系统,也就是常简说的Linux操作系统。
    GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。
    g++:GCC中的GUN C++ Compiler(C++编译器)
    gcc:GCC中的GUN C Compiler(C 编译器)
      由于编译器是可以更换的,所以gcc不仅仅可以编译C文件,更准确的说法是:gcc调用了C compiler,而g++调用了C++ compiler。
      gcc and g++分别是gnu的c & c++编译器,gcc/g++在执行编译工作的时候,总共需要4步:

    1.预处理,生成.i的文件[预处理器cpp];
    2.将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs];
    3.由汇编变为目标代码(机器代码)生成.o的文件[汇编器as];
    4.连接目标代码,生成可执行程序[链接器ld]。
    

      gcc和g++的主要区别:

    1. 对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的)

    2. 对于 .c和.cpp文件,g++则统一当做cpp文件编译

    3. 使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL

    4. gcc在编译C文件时,可使用的预定义宏是比较少的

    5. gcc在编译cpp文件时/g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏,这些宏如下:

      #define GXX_WEAK 1
      #define __cplusplus 1
      #define __DEPRECATED 1
      #define GNUG 4
      #define __EXCEPTIONS 1
      #define private_extern extern

    6. 在用gcc编译c++文件时,为了能够使用STL,需要加参数 –lstdc++ ,但这并不代表 gcc –lstdc++ 和 g++等价,它们的区别不仅仅是这个。
      主要参数:
      -g - turn on debugging (so GDB gives morefriendly output)
      -Wall - turns on most warnings
      -O or -O2 - turn on optimizations
      -o - name of the output file
      -c - output an object file (.o)
      -I - specify an includedirectory
      -L - specify a libdirectory
      -l - link with librarylib.a

    使用示例:g++ -ohelloworld -I/homes/me/randomplace/include helloworld.C
    以上参考来源于下面链接。感谢原作者。https://www.cnblogs.com/oxspirt/p/6847438.html


    预处理器主要负责以下的几处:

    1.宏的替换
    2.删除注释
    3.处理预处理指令,如#include,#ifdef
    

    下面进行实例验证。
    现找到:
    在这里插入图片描述
    点击进入你的工程目录对应的cpp位置。
    输入g++ –E main.cpp>main.i进行预编译生成main.i文件。
    在这里插入图片描述
    打开main.i发现里面内容实在很多,前面一大堆不知道啥,大概就是在干#include 和#include "1.h"这两件事。到文件最后:
    在这里插入图片描述
    看看行号,就简简单单的main函数,就那么多行了。重点是:main函数中的头文件被替换了(不管替换成什么了),注释不见了,宏被替换了。
    同理生成1.i文件
    在这里插入图片描述
    也是相同的处理。
    当相同地编译运行1.h文件时,有了如下警告:
    在这里插入图片描述
    单词“pragma”就是编译指示的意思,警告你1.h只在main文件中编译一次,此操作不可行。


    编译和优化

    这个步骤作用如下:

    词法分析 – 识别单词,确认词类;比如int i;知道int是一个类型,i是一个关键字以及判断i的名字是否合法。
    语法分析 – 识别短语和句型的语法属性;
    语义分析 – 确认单词、短语和句型的语义特征;
    代码优化 – 修辞、文本编辑;
    代码生成 – 生成译文。
    内联函数的替换就发生在这一阶段
    

    预编译和编译都是GCC来完成的。

    编译

    按照g++ –E main.cpp>main.i文件的语句写g++ –E main.i>main.s,然后报错了。
    在这里插入图片描述
    然后用g++ –E main.cpp>main.s也是相同的报错。
    后来搜到有人使用g++ -S main.cpp,生成成功。.s文件表示是汇编文件,用编辑器打开就都是汇编指令。

    	.file	"main.cpp"
    .lcomm __ZStL8__ioinit,1,1
    	.def	___main;	.scl	2;	.type	32;	.endef
    	.section .rdata,"dr"
    LC0:
    	.ascii "this is a macro!\0"
    LC1:
    	.ascii "pause\0"
    	.text
    	.globl	_main
    	.def	_main;	.scl	2;	.type	32;	.endef
    _main:
    	leal	4(%esp), %ecx
    	andl	$-16, %esp
    	pushl	-4(%ecx)
    	pushl	%ebp
    	movl	%esp, %ebp
    	pushl	%ecx
    	subl	$36, %esp
    	call	___main
    	call	__Z5printv
    	movl	$LC0, 4(%esp)
    	movl	$__ZSt4cout, (%esp)
    	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    	movl	$__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
    	movl	%eax, %ecx
    	call	__ZNSolsEPFRSoS_E
    	subl	$4, %esp
    	movl	$1, -12(%ebp)
    	movl	$5, -16(%ebp)
    	movl	-12(%ebp), %edx
    	movl	-16(%ebp), %eax
    	addl	%edx, %eax
    	movl	%eax, -20(%ebp)
    	movl	-20(%ebp), %eax
    	movl	%eax, (%esp)
    	movl	$__ZSt4cout, %ecx
    	call	__ZNSolsEi
    	subl	$4, %esp
    	movl	$__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
    	movl	%eax, %ecx
    	call	__ZNSolsEPFRSoS_E
    	subl	$4, %esp
    	movl	$LC1, (%esp)
    	call	_system
    	movl	$0, %eax
    	movl	-4(%ebp), %ecx
    	leave
    	leal	-4(%ecx), %esp
    	ret
    	.def	___tcf_0;	.scl	3;	.type	32;	.endef
    ___tcf_0:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$8, %esp
    	movl	$__ZStL8__ioinit, %ecx
    	call	__ZNSt8ios_base4InitD1Ev
    	leave
    	ret
    	.def	__Z41__static_initialization_and_destruction_0ii;	.scl	3;	.type	32;	.endef
    __Z41__static_initialization_and_destruction_0ii:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$24, %esp
    	cmpl	$1, 8(%ebp)
    	jne	L4
    	cmpl	$65535, 12(%ebp)
    	jne	L4
    	movl	$__ZStL8__ioinit, %ecx
    	call	__ZNSt8ios_base4InitC1Ev
    	movl	$___tcf_0, (%esp)
    	call	_atexit
    L4:
    	leave
    	ret
    	.def	__GLOBAL__sub_I_main;	.scl	3;	.type	32;	.endef
    __GLOBAL__sub_I_main:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$24, %esp
    	movl	$65535, 4(%esp)
    	movl	$1, (%esp)
    	call	__Z41__static_initialization_and_destruction_0ii
    	leave
    	ret
    	.section	.ctors,"w"
    	.align 4
    	.long	__GLOBAL__sub_I_main
    	.ident	"GCC: (i686-posix-sjlj, built by strawberryperl.com project) 4.9.2"
    	.def	__Z5printv;	.scl	2;	.type	32;	.endef
    	.def	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc;	.scl	2;	.type	32;	.endef
    	.def	__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_;	.scl	2;	.type	32;	.endef
    	.def	__ZNSolsEPFRSoS_E;	.scl	2;	.type	32;	.endef
    	.def	__ZNSolsEi;	.scl	2;	.type	32;	.endef
    	.def	_system;	.scl	2;	.type	32;	.endef
    	.def	__ZNSt8ios_base4InitD1Ev;	.scl	2;	.type	32;	.endef
    	.def	__ZNSt8ios_base4InitC1Ev;	.scl	2;	.type	32;	.endef
    	.def	_atexit;	.scl	2;	.type	32;	.endef
    
    

    汇编咱也看不懂,咱也不敢说,只是看明白了1+5,add 了存在1和5的地址。
    同理生成1.s文件。也是看不懂。

    优化

      优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。上图中,我们将优化阶段放在编译程序的后面,这是一种比较笼统的表示。
    对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。
      后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。
      经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。
      详细可见下面链接,感谢原作者。
    https://blog.csdn.net/qq_30647245/article/details/80558

    生成目标文件

      这一步是从汇编程序生成目标代码(机器代码)。
    GCC语句是g++ -c main.cpp
      功能:.o是GCC生成的目标文件,除非你是做编译器和连接器调试开发的,否则打开这种.o没有任何意义。二进制机器码一般人也读不了,就是下图这样。
    在这里插入图片描述

    链接

      连接各个.o目标代码文件,生成可执行程序。必须已经生成1.o和main.o,这样才能链接需要的.o文件生成.exe。
    g++语句为 g++ 1.o main.o -o Test.exe
    双击生成的Test.exe
    在这里插入图片描述
    这样,完整的从源程序到exe就结束了。其中的g++命令语句参考链接为https://www.jianshu.com/p/e5e9925a6158,感谢原作者。


    下面终于要进入今天的正题链接库是怎么回事?

    什么是库?

      库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。

    在这里插入图片描述

    动态静态的区别

      上面刚刚提到了静态链接库和动态链接库,如果采用静态链接库,则无论你愿不愿意,在链接程序时,lib中的指令都被直接(通过拷贝的方式)包含在最终生成的EXE文件中了,这是在第5步链接程序中可以知道的。动态链接库在链接程序时,只是记录了少量必要信息,在实际程序执行时才动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。所以,当单独执行debug文件或者release文件中的exe的时候,静态链接库不需要放在exe的目录下,但是动态链接库必须放在exe的目录下。


    静态链接库

      之所以成为静态库,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
      试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:

    1	静态库对函数库的链接是放在编译时期完成的。
    2	程序在运行时与函数库再无瓜葛,移植方便。
    3	浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
    

    静态链接库的优点和缺点:
    1.代码装载速度快,执行速度略比动态链接库快;
    2.只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
    3.使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;

    lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。

    静态链接库的创建方法

      首先创建静态库,要封装的函数只是一系列子函数:

    //main_test.cpp
    #include <iostream>
    #include "1.h"
    #define AA "this is a macro!"
    
    void main_test()
    {
    #ifdef AA  //我是main.cpp的注释
    	print();
    	std::cout << AA << std::endl;
    #endif
    
    	int a = 1;
    	int b = 5;
    	int c = a + b;
    	std::cout << c << std::endl;
    }
    
    void my_func()
    {
    	std::cout << "我是第二个函数" << std::endl;
    }
    
    //main_test.h
     #pragma once
     void main_test();
     void my_func();
    

    在工程属性里更改如下:不生成exe,就生成静态库lib
    在这里插入图片描述
    按F7或者点击生成解决方案之后就可以在Debug里看到生成的lib文件了。

    lib的调用

    将这里的.lib文件和.h文件拷贝,到要引用的目录下,#include “*.h”和#pragma comment (lib,".lib"),在当前工程中就可以直接用里面的函数了。lib文件还有其他方法添加,这里就不赘述了。


    动态链接库

      代码复用是提高软件开发 效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架, 如ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点比较多,总结起来有4点:

    1 暴露了源代码;
    2	容易与程序员的“普通”代码发生命名冲突;
    3	多份拷贝,造成存储浪费;
    4	更新功能模块比较困难。
    

      实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,就提出了“二进制级别”的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒复用”。
      另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。

    lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。

    动态链接库的创建方法

    创建动态库的方法略有不同。先是将属性里的配置类型改为动态库(dll)
    在头文件中需要加入宏命令。

    #pragma once
    
    #ifdef __DLLEXPORT
    #define __DLL_EXP _declspec(dllexport)    // 导出函数 - 生成dll文件时使用
    #else
    #define __DLL_EXP _declspec(dllimport)    // 导入函数 -使用dll是使用
    #endif // __DLLEXPORT
    
    void main_test();
    void my_func();
    

    如果是生成dll文件,需要在属性->C\C+±>预处理器->预处理器定义中加上宏。
    在这里插入图片描述
      生成解决方案后在Debug中就生成了 .dll和.lib文件。我开始的时候怎么都只有dll,没有lib,后来参考别人的建议,在在工程上右键 -> 添加 -> 新建项 -> 选"模块定义文件(.def)" -> 随便输入个名字 -> 添加,添加的名字任意,可以默认,里面只有LIBRARY这一句,不用管,这时候再生成解决方案,Debug里就有lib和dll了。
      def文件中内容要加上函数名

    //Source.def
    LIBRARY Project2
    
    EXPORTS
    main_test @1
    my_func @2
    

    注意:.def文件中的第一条 LIBRARY 语句不是必须的,但LIBRARY 语句后面的 DLL 的名称必须正确,即与生成的动态链接库的名称必须匹配。
      EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。
    main_test.cpp文件为:

    #include <iostream>
    #include "main_test.h"
    #pragma comment(lib, "Project2.lib")
    
    void main()
    {
    	main_test();
    	my_func();
    	system("pause");
    }
    

    F7生成解决方案就会生成dll、lib文件。

    dll的使用

    调用分为2种。

    隐式链接

    需要用到.dll、.lib和.h文件。
    这里的.lib文件不再是静态链接库的意思,而是导入库的意思。
    将三个文件复制到需要使用的工程目录中,
    将.dll和.lib放在一个文件夹内。
    在这里插入图片描述
    测试工程main.cpp

    #include <iostream>
    #include "main_test.h"
    #pragma comment(lib, "ku\\Project2.lib")
    
    void main()
    {
    	main_test();
    	my_func();
    	system("pause");
    }
    

    运行结果
    在这里插入图片描述

    显式链接

    需要用到.dll和.h文件。
      我暂时不需要用显式调用,所以以后用到再说。


    That’s all. Let’s enjoy it ~

    展开全文
  • 静态链接

    2014-11-07 16:31:22
    编写静态链接库的代码实现,可以用来参考一下
  • 动态链接库和静态链接库区别,让你真正理解动态链接库和静态链接库的区别。
  • 易语言静态链接

    2015-03-02 17:14:25
    易语言静态链接器解决无法定位链接器!请检查 tools\link.ini 中的配置是否正确的错误,易语言5.X版本以上编译为静态编译,静态编译需要借助VC编译器,如果编译器配置不正确或者没安装将会出现以上信息。此编译器全...
  • openssl的静态链接库 libeay32.lib 和 ssleay32.lib,包含64位和32位
  • 本文描述了Linux下使用动态链接库和静态链接库的项目建立及单步调试
  • C++静态链接库与动态链接库理解

    千次阅读 2019-06-23 14:35:59
    原文:...看到一篇介绍静态链接库和动态链接库的文章,写的太好了,遂转载过来分享一下。 这次分享的宗旨是——让大家学会创建与使用静态库、动态库,知道静态库与动态库的区别,知道...

    看到一篇介绍静态链接库和动态链接库的文章,写的太好了,遂转载过来分享一下。

    这次分享的宗旨是——让大家学会创建与使用静态库、动态库,知道静态库与动态库的区别,知道使用的时候如何选择。这里不深入介绍静态库、动态库的底层格式,内存布局等,有兴趣的同学,推荐一本书《程序员的自我修养——链接、装载与库》。

    1 什么是库

    库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常

    本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。

    所谓静态、动态是指链接。回顾一下,将一个程序编译成可执行程序的步骤:

    2 静态库

    之所以成为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

    试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:

    •  静态库对函数库的链接是放在编译时期完成的。
    • 程序在运行时与函数库再无瓜葛,移植方便。
    • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

    下面编写一些简单的四则运算C++类,将其编译成静态库给他人用,头文件如下所示:

    
     
    1. #StaticMath.h头文件
    2. #pragma once
    3. class StaticMath
    4. {
    5. public:
    6. StaticMath( void);
    7. ~StaticMath( void);
    8. static double add(double a, double b); //加法
    9. static double sub(double a, double b); //减法
    10. static double mul(double a, double b); //乘法
    11. static double div(double a, double b); //除法
    12. void print();
    13. };

    Linux下使用ar工具、Windows下vs使用lib.exe,将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索。一般创建静态库的步骤如图所示:

    3 Linux下创建与使用静态库

    3.1 Linux静态库命名规则

    Linux静态库命名规范,必须是"lib[your_library_name].a":lib为前缀,中间是静态库名,扩展名为.a。

    3.2 创建静态库(.a)

    通过上面的流程可以知道,Linux创建静态库过程如下:

    首先,将代码文件编译成目标文件.o(StaticMath.o)

    g++ -c StaticMath.cpp
     

    注意带参数-c,否则直接编译为可执行文件

    然后,通过ar工具将目标文件打包成.a静态库文件

    ar -crv libstaticmath.a StaticMath.o
     

    生成静态库libstaticmath.a

    大一点的项目会编写makefile文件(CMake等等工程管理工具)来生成静态库,输入多个命令太麻烦了。

    3.3 使用静态库

    编写使用上面创建的静态库的测试代码:

    
     
    1. #include "StaticMath.h"
    2. #include <iostream>
    3. using namespace std;
    4. int main(int argc, char* argv[])
    5. {
    6. double a = 10;
    7. double b = 2;
    8. cout << "a + b = " << StaticMath::add(a, b) << endl;
    9. cout << "a - b = " << StaticMath::sub(a, b) << endl;
    10. cout << "a * b = " << StaticMath::mul(a, b) << endl;
    11. cout << "a / b = " << StaticMath::div(a, b) << endl;
    12. StaticMath sm;
    13. sm.print();
    14. system( "pause");
    15. return 0;
    16. }

    Linux下使用静态库,只需要在编译的时候,指定静态库的搜索路径(-L选项)、指定静态库名(不需要lib前缀和.a后缀,-l选项)。

     g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath
     

    •  -L:表示要连接的库所在目录
    • -l:指定链接时需要的动态库,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称。

    4 动态库

    通过上面的介绍发现静态库,容易使用和理解,也达到了代码复用的目的,那为什么还需要动态库呢?为什么需要动态库,其实也是静态库的特点导致。空间浪费是静态库的一个问题。另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。

    动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新

    动态库特点总结:

    • 动态库把对一些库函数的链接载入推迟到程序运行的时期。
    • 可以实现进程之间的资源共享。(因此动态库也称为共享库)
    • 将一些程序升级变得简单。
    • 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。

    与创建静态库不同的是,不需要打包工具(ar、lib.exe),直接使用编译器即可创建动态库。

    5 Linux下创建与使用动态库

    5.1 linux动态库的命名规则

    动态链接库的名字形式为 libxxx.so,前缀是lib,后缀名为“.so”。

    • 针对于实际库文件,每个共享库都有个特殊的名字“soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
    • 在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件(lib+soname+.so)。

    5.2 创建动态库(.so)

    编写四则运算动态库代码:

    
     
    1. #pragma once
    2. class DynamicMath
    3. {
    4. public:
    5. DynamicMath( void);
    6. ~DynamicMath( void);
    7. static double add(double a, double b); //¼Ó·¨
    8. static double sub(double a, double b); //¼õ·¨
    9. static double mul(double a, double b); //³Ë·¨
    10. static double div(double a, double b); //³ý·¨
    11. void print();
    12. };

    首先,生成目标文件,此时要加编译器选项-fpic,-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。

    g++ -fPIC -c DynamicMath.cpp
     

    然后,生成动态库,此时要加链接器选项-shared

    g++ -shared -o libdynmath.so DynamicMath.o
     

    其实上面两个步骤可以合并为一个命令

    g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp
     

     5.3 使用动态库

    编写使用动态库的测试代码:

    
     
    1. #include "../DynamicLibrary/DynamicMath.h"
    2. #include <iostream>
    3. using namespace std;
    4. int main(int argc, char* argv[])
    5. {
    6. double a = 10;
    7. double b = 2;
    8. cout << "a + b = " << DynamicMath::add(a, b) << endl;
    9. cout << "a - b = " << DynamicMath::sub(a, b) << endl;
    10. cout << "a * b = " << DynamicMath::mul(a, b) << endl;
    11. cout << "a / b = " << DynamicMath::div(a, b) << endl;
    12. DynamicMath dyn;
    13. dyn.print();
    14. return 0;
    15. }

    引用动态库编译成可执行文件(跟静态库方式一样):

    g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath
     

    然后运行:./a.out,发现竟然报错了!!!

    可能大家会猜测,是因为动态库跟测试程序不是一个目录,那我们验证下是否如此:

    发现还是报错!!!那么,在执行的时候是如何定位共享库文件的呢?

    1. 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
    2.  对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib 目录找到库文件后将其载入内存。

    如何让系统能够找到它:

    • 如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。
    • 如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:
    • 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
    • 运行ldconfig ,该命令会重建/etc/ld.so.cache文件

    我们将创建的动态库复制到/usr/lib下面,然后运行测试程序。

    6 附件:Linux下库相关命令

    6.1 g++(gcc)编译选项

    • -shared :指定生成动态链接库。
    • -static :指定生成静态链接库。
    • -fPIC :表示编译为位置独立的代码,用于编译共享库。目标文件需要创建成位置无关码, 念上就是在可执行程序装载它们的时候,它们可以放在可执行程序的内存里的任何地方。
    • -L. :表示要连接的库所在的目录。
    • -l:指定链接时需要的动态库。编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a/.so来确定库的名称。
    • -Wall :生成所有警告信息。
    • -ggdb :此选项将尽可能的生成gdb 的可以使用的调试信息。
    • -g :编译器在编译的时候产生调试信息。
    • -c :只激活预处理、编译和汇编,也就是把程序做成目标文件(.o文件) 。
    • -Wl,options :把参数(options)传递给链接器ld 。如果options 中间有逗号,就将options分成多个选项,然后传递给链接程序​

    6.2 nm命令

    有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种:

    • 一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
    • 一种是库中定义的函数,用T表示,这是最常见的;
    • 一种是所谓的弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。

    $nm libhello.h

    6.3 ldd命令

    ldd命令可以查看一个可执行程序依赖的共享库,例如我们编写的四则运算动态库依赖下面这些库:

    7 总结

    二者的不同点在于代码被载入的时刻不同

    • 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大
    • 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小

    动态库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。带来好处的同时,也会有问题!如经典的DLL Hell问题,关于如何规避动态库管理问题,可以自行查找相关资料。

    展开全文
  • 在阅读本文之前,小编先给大家介绍一篇相关文章:Linux静态链接库使用类模板的快速排序算法 大家首先看下以上的文章对理解下面的知识点会有很大的帮助。 当模板遇到静态链接库会发生什么呢。 我们先按照常规思路去...
  • Go语言的静态链接与动态链接

    万次阅读 2021-08-27 22:45:20
    Go语言的静态链接与动态链接@TOC Go语言在默认情况下是静态链接的: 但是,有一些库可能会导致动态链接: 这时候可以增加 -ldflags="-extldflags --static" 参数来进行静态链接

    Go语言在默认情况下是静态链接的:
    go语言默认静态链接

    但是,有一些库可能会导致动态链接:
    某库导致动态链接

    这时候如果你确实需要静态链接,那么可以增加 -ldflags="-extldflags --static" 参数来进行静态链接,即这个命令:

    go build -ldflags="-extldflags --static"
    

    增加静态链接参数

    当然,还存在一些第三方库,因调用了一些 glibc 中不支持静态链接的函数,而导致无法静态链接:
    某第三方库导致静态链接失败

    对于这类情况,如果坚持一定要静态链接,那么可以弃用 glibc 库,改用 musl libc 库。

    如果你使用的操作系统是 Debian / Ubuntu ,那么可以运行如下命令安装 musl libc 库:

    sudo apt-get install musl-dev musl-tools
    

    安装 musl libc 库

    然后使用 musl libc 库来静态链接,命令是:

    CC=musl-gcc go build -tags musl -ldflags="-extldflags --static"
    

    使用 musl libc 库来静态链接

    于是,静态链接成功。

    展开全文
  • 介绍静态链接库和动态链接库作用和区别,制作静态库和动态库的方法,编程测试自己编写的两种库
  • 静态链接与动态链接的区别和使用

    万次阅读 多人点赞 2018-11-27 13:34:59
     静态链接:譬如让书本和白板上的笔记之间做静态链接,就是把白板上的笔记抄在书上书和笔记形成一个整体(可执行程序),这个时候把白板上的内容擦掉也没关系,因为已经整合到书上了。静态链接的优点是“效率高”...
  • Linux-动态链接与静态链接对比(动态库和静态库)

    千次阅读 多人点赞 2017-12-14 17:52:46
    一、库的基础概念: 在windows平台和linux平台下都大量存在着库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。...按照库的使用方式又可分为动态库和静态库,在不同平台下...
  • 准备好静态链接库之后,要在VC中使用需要:#include ,添加静态库SQLite.lib(具体步骤同添加动态链接库头文件和导入库lib把文件一样)。本例是使用静态链接库打开数据库、写入数据、关闭数据库文件的。注意:静态...
  • 静态链接和动态链接及其区别

    千次阅读 2020-01-11 23:11:24
    我们知道编译器会将程序源文件编译成一个个目标文件,并最终链接成可执行程序或者静态库或动态库,静态库一般都是指的由静态链接的目标文件集合,而动态库一般是动态链接目标生成的目标集合,那么静态链接和动态链接...
  • VC6下的LIB静态链接库的创建、加载的示例程序,有相关说明。代码中注释较为丰富,适合学习使用。版本比较老,就免费了!
  • 【转载】c++ 静态链接和动态链接

    千次阅读 2018-01-02 14:32:36
    C++静态库与动态库 ...这里不深入介绍静态库、动态库的底层格式,内存布局等,有兴趣的同学,推荐一本书《程序员的自我修养——链接、装载与库》。 什么是库 库是写好的现有的,成熟的,可以复
  • 静态链接的优点* 1、程序的启动速度和运行速度相对于动态链接快。 静态链接的缺点 1、内存的磁盘空间的浪费 当多个进程静态链接同一个静态库时,会复制多个副本,每个程序链接静态库的时候都会链接一个副本到目标...
  • 静态链接和动态链接区别

    万次阅读 多人点赞 2019-09-22 22:17:18
    1.静态链接与动链接的区别 在C语言中,我们知道要生成可执行文件,必须经历两个阶段,即编译、链接。 在编译过程中,只有编译,不会涉及到链接。 在链接过程中,静态链接和动态链接就出现了区别。静态链接的过程...
  • 静态链接库和动态链接库的区别

    千次阅读 2018-04-23 20:45:30
    一、静态链接库的使用 静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件。 在VS2015平台上,创建一个静态库(.lib)项目方案,选择【创建项目/Win32/Win32控制台...
  • 静态链接库、动态链接库使用方法

    千次阅读 2017-08-27 20:18:44
    总结一下动态链接库和静态链接库。
  • 动态链接库和静态链接库的区别

    千次阅读 2019-08-12 02:12:13
    静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要... 静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 538,444
精华内容 215,377
关键字:

静态链接