Linux下Makefile中动态链接库和静态链接库的生成与调用

  背景:写这篇博客的原因是:最近在搞嵌入式,需要交叉编译opencv库文件,自己写Makefile,通过arm-linux-g++编译、链接、生成可执行文件,从而实现了移植的过程。平台是Toradex的Apalis TK1,三千多元,买回来我就后悔了,全是英文资料,还各种Bug,迟迟无法上手。早知如此,还不如直接买Nvidia的Jetson TK1呢。

书归正传,今天写一下Makefile文件中,动态链接库和静态链接库的生成与调用。

一、概念

  动态链接库:是一种不可执行的二进制程序文件,它允许程序共享执行特殊任务所必需的代码和其他资源。Windows平台上动态链接库的后缀名是”.dll”,Linux平台上的后缀名是“.so”。Linux上动态库一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。

  静态链接库:这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。

Makefile:利用IDE开发调试的人员可能对Makefile不大理解,其实Makefile就是完成了IDE中的编译、链接、生成等工作,并遵循shell脚本中变量定义与调用的规则。

二、编写Makefile实现编译与链接

1、准备文件

  我们写一个简单的工程吧,此工程包含3个文件,分别是main.cpp,func.cpp和func.h。代码如下:

  1)main.cpp源文件:包含入口函数 int main()。该源文件中添加了“func.h”头文件,在入口函数中调用func()函数。func函数中在func.cpp中定义,在func.h中原型声明。

# main.cpp
#include "func.h"
int main()
{
    func();
    return 0;
}

2)func.h头文件:对void func()函数进行原型声明。

 

3)func.cpp源文件:对void func()函数的定义或实现。

# func.cpp
#include "func.h"
void func()
{
    std::cout << "Hello World !" << std::endl;
}

2、编写Makefile文件

1)定义变量

  首先定义SOURCE,OBJS和TARGET变量,用于指代我们项目中的源文件、目标文件和可执行文件。

2) 设置编译参数

  CC:配置编译器为g++,

  LIBS:需要调用的链接库(-l开头,去掉lib和.so。例:对 libopencv_core.so链接库的调用要写作:-lopencv_core),

  LDFLAGS:链接库的路径(-L开头),

  INCLUDE:头文件的路径。

3)链接生成

  此步骤生成可执行文件(ELF),链接需要用到目标文件,由下一步产生

4)编译

  此步骤生成目标文件(.o)

5)清理

  此步骤清理可执行文件和所有的目标文件

#######################
# Makefile
#######################
# source object target
SOURCE := main.cpp func.cpp
OBJS   := main.o func.o
TARGET := main
# compile and lib parameter
CC      := g++
LIBS    :=
LDFLAGS := -L.
DEFINES :=
INCLUDE := -I.
CFLAGS  :=
CXXFLAGS:=
# link
#$(TARGET):$(OBJS)
    $(CC) -o $@ $^
# compile
#$(OBJS):$(SOURCE)
    $(CC) -c main.cpp -o main.o
    $(CC) -c func.cpp -o func.o
# clean
clean:
    rm -fr *.o
    rm -fr $(TARGET)

上述Makefile是将编译和链接两个步骤分开写的,我们同样可以直接从源文件生成可执行文件,自动进行编译链接等工作。

方法:将上述Makefile中的:

# link
#$(TARGET):$(OBJS)
    $(CC) -o $@ $^
# compile
#$(OBJS):$(SOURCE)
    $(CC) -c main.cpp -o main.o
    $(CC) -c func.cpp -o func.o

修改为:

all:
    $(CC) -o $(TARGET) $(SOURCE)

其他内容,不作变化。

6)编译、执行、清理

make
./main
make clean

NOTE:在执行make的时候会出现Makefile:19: *** missing separator.  Stop的错误,提示没有分割符,在makefile中规定必须以TAB键进行缩进.

执行make clean可以清楚当前目录下编译的文件.

2、动态链接库的调用

引言:在第一部分中,我们将main.o和func.o两个目标文件进行链接,便生成了main可执行文件。如果甲方并没有提供func.cppfunc.o,只是提供了libfunc.so这个链接库,我们如何生成可执行文件呢?下文就是讲述如何利用动态库链接生成可执行文件。

Makefile如下:

  1)编译的时候需要通过INCLUDE指明头文件的路径

[A1] 这里需要注意的是,指明动态库名称时需要“掐头去尾”,例:我们需要用到 libfunc.so库,LIBS必须定义为 -lfunc 。

/*[A1]

LDFLAGS指明动态库的路径;

LIBS指明动态库的名称,需注意动态库名称需要“掐头去尾”

*/

3)执行的时候,需要把libfunc.so动态库拷贝到系统环境变量包含的路径下(比如/lib/usr/lib,这样程序在运行时才能调用到动态库。

#######################
# Makefile
#######################
# target
TARGET := main
# compile and lib parameter
CC      := g++
LDFLAGS := -L/path/to/libfunc.so
LIBS    := -lfunc
DEFINES :=
INCLUDE := -I/path/to/func.h
CFLAGS  :=
CXXFLAGS:=
# link
$(TARGET):main.o
    $(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
#compile
main.o:main.cpp
    $(CC) $(INCLUDE) -c $^crm -fr $(TARGET)
clean:
  rm -fr *.o
    rm -fr $(TARGET)

四、静态链接库的生成和调用

1、静态链接库的生成

引言:仍然利用上文中的main.cpp, func.cpp和func.h文件。下面,我们将func.cpp源文件制作成静态链接库func.a,然后调用该静态库对main.cpp进行编译链接。

Makefile如下:

注意:AR:配置链接器为ar

#######################
# Makefile
######################
# compile and lib parameter
CC      := g++
LIBS    :=
LDFLAGS :=
DEFINES :=
INCLUDE := -I.
CFLAGS  :=
CXXFLAGS:=
# link parameter
AR  := ar
LIB := func.a
#link
$(LIB):func.o
    $(AR) -r $@ $^
#compile
func.o:func.cpp
    $(CC) -c $^ -o $@
# clean
clean:
   rm -fr *.o

执行make命令之后,就可以在当前目录生成func.a的静态链接库了。

注意:静态链接库必须“以.a结尾”。

2、静态链接库的调用

  引言:在第一部分中,我们将main.o和func.o两个目标文件进行链接,便生成了main可执行文件;第二部分,我们将main.o和libfunc.so进行链接,也可以生成main可执行文件。如果我们既没有func.o也没有func.so,该如何生成可执行文件呢?下文就是讲述如何利用静态库func.a链接生成可执行文件。

Makefile如下:

  1)编译的时候需要通过INCLUDE指明头文件的路径

  2)链接的时候需要通过LDFLAGS LIBS指明静态库的路径和名称。这里不需要像动态库那样掐头去尾,直接写作func.a即可

  3)执行的时候,不需要拷贝func.a至环境变量包含的路径,直接执行即可。

#######################
# Makefile
#######################
# target
TARGET := main
# compile and lib parameter
CC      := g++
LDFLAGS := -L.
LIBS    := func.a
DEFINES :=
INCLUDE := -I.
CFLAGS  :=
CXXFLAGS:=
# link
$(TARGET):main.o
    $(CC) -o $@ $^ $(LIBS)
#compile
main.o:main.cpp
    $(CC) -c $^ -o $@
# clean
clean:
    rm -fr *.o
    rm -fr $(TARGET)

 

 

linux ar 命令的使用说明和例子

用途说明  

  创建静态库.a文件。用C/C++开发程序时经常用到,但我很少单独在命令行中使用ar命令,一般写在makefile中,有时也会在shell脚 本中用到。关于Linux下的库文件、静态库、动态库以及怎样创建和使用等相关知识,参见本文后面的相关资料【3】《关于Linux静态库和动态库的分析》。  

  常用参数  格式:ar rcs  libxxx.a xx1.o xx2.o  

  参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。 默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。

  参数c:创建一个库。不管库是否存在,都将创建。  

  参数s:创建目标文件索引,这在创建较大的库时能加快时间。(补充:如果不需要创建索引,可改成大写S参数;如果。a文件缺少索引,可以使用ranlib命令添加)  

  格式:ar t libxxx.a  显示库文件中有哪些目标文件,只显示名称。  

  格式:ar tv libxxx.a  显示库文件中有哪些目标文件,显示文件名、时间、大小等详细信息。  

  格式:nm -s libxxx.a  显示库文件中的索引表。  

  格式:ranlib libxxx.a  为库文件创建索引表。  

使用示例  

  示例一 在shell脚本中使用Bash代码OS=`uname -r`  ar rcs libhycu.a.$OS *.o  

  示例二 在makefile中使用 Makefile代码 $(BIN1): $(BIN1_OBJS) ar rcs $@ $^  

  示例三 创建并使用静态库  

[A2]Vi 多个源文件*/

  第二步:将test.c编译成目标文件。  gcc -c test.c 如果test.c无误,就会得到test.o这个目标文件。     如果是gcc test.c 则默认生成a.out文件

  第三步:由.o文件创建静态库。  

ar rcs libtest.a test.o
[A1]
./main

 

示例四创建并使用动态库  

  第一步:编辑源文件,test.h test.c main.c。其中main.c文件中包含main函数,作为程序入口;test.c中包含main函数中需要用到的函数。  vi test.h test.c main.c  

  第二步:将test.c编译成目标文件。  gcc -c test.c  前面两步与创建静态库一致。

  第三步:由.o文件创建动态库文件。  gcc -shared -fPIC -o libtest.so test.o 

  第四步:在程序中使用动态库。  gcc -o main main.c -L. -ltest  当静态库和动态库同名时,gcc命令将优先使用动态库。  

  第五步:执行。  LD_LIBRARY_PATH=. ./main 


    如果想创建一个动态链接库,可以使用 GCC 的-shared选项。输入文件可以是源文件、汇编文件或者目标文件。

    -shared该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。

    另外还得结合-fPIC选项。-fPIC 选项作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code);这样一来,产生的代码中就没有绝对地址了,全部使用相对地址,所以代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。