Linux下C++程序编译链接的学习例子
一、项目例子的整体结构
-
本项目旨在简单学习C++编译连接的过程以及Makefile和Cmake的简单语法,项目结构如下:
. ├── appC │ ├── include │ ├── lib │ └── src │ ├── CMakeLists.txt │ ├── main.cpp │ └── Makefile ├── cleanAll.sh ├── CMakeLists.txt ├── libA │ ├── output │ │ ├── include │ │ └── lib │ └── src │ ├── A.cpp │ ├── ADll.h │ ├── A.h │ ├── CMakeLists.txt │ └── Makefile ├── libB │ ├── include │ ├── lib │ ├── output │ │ ├── include │ │ └── lib │ └── src │ ├── B.cpp │ ├── BDll.h │ ├── B.h │ ├── CMakeLists.txt │ └── Makefile └── readme.md -
依赖关系为:main->B->A,cleanAll.sh用来删除生成的*.o *.a *.so文件,以便学习不同的链接方式。
二、代码结构
-
main代码如下:
#include "B.h" #include <iostream> using namespace std; int main (int argc, char *argv[]) { B objB; int result = objB.func(1, 2); cout << "result = " << result << endl; } -
libA代码如下:
/** *********A.cpp************ */ #include "A.h" #include <iostream> using namespace std; A::A() { cout << "A ctor" << endl; } A::~A() { cout << "A dtor" << endl; } int A::func(int x, int y) { return (x + y) * 2; } /** *********A.h************ */ #ifndef _A_H_ #define _A_H_ #include "ADll.h" class LIBA_API A { public: A(); ~A(); int func(int x, int y); }; #endif -
libB代码如下:
/** *******************B.cpp******************** */ #include "B.h" #include "A.h" #include <iostream> using namespace std; class BImpl { public: BImpl() {}; ~BImpl() {}; int func(int x, int y){ return objA.func(x, y); } private: A objA; }; B::B() : _impl(new BImpl) { cout << "B ctor" << endl; } B::~B() { cout << "B dtor" << endl; delete _impl; } int B::func(int x, int y){ return _impl->func(x, y); } /** ************B.h******************* */ #ifndef _B_H_ #define _B_H_ #include "BDll.h" class BImpl; class LIBB_API B { public: B(); ~B(); int func(int x, int y); private: BImpl *_impl; }; #endif -
以libA下的makefile为例学习makefile:
CXX = g++ #编译器 AR = ar # 对.o文件进行归档,生成库 CXXFLAGS = -shared -g -Wall -fPIC -std=c++11 -I./ #g++参数:-shared 调用动态库 -g 可执行程序包含调试信息 -Wall 编译后显示所有警告 -fPIC 真正的共享.so -std=c++11 支持c++11标准; -I 后跟头文件地址 LDFLAGS = -L./ # 库文件地址 ### -Wl,-rpath=your_lib_dir 只有-L选项时有可能编译通过但是执行时not found 库,因为-L只有编 译时有效,可以通过加上此命令记住链接库的位置; SRCS = $(wildcard *.cpp) # wildcard 函数,展开所有的.cpp文件 OBJS = $(patsubst %.cpp, %.o, $(SRCS)) patsubst函数 将.cpp全部替换成.o并返回,等于objs等于.cpp编译的.o文件 $(info SRCS = $(SRCS)) $(info OBJS = $(OBJS)) # info make的打印函数 TARGET = libA.so libA.a # 编译目标 OUTPUT = ../output #输出位置 all: clean $(TARGET) install copy2B # 让所有的操作顺次执行, .PHONY: clean install copy2B #因为make的规则是 目标 :依赖 ,当 main : main.c 时,为了从依赖获取目标,就会执行之后的规则; #但是当clean: 后没有依赖时,make会认为依旧获取到目标,因此不再执行clean后的规则,因此 #引用.PHONY关键字,意为欺骗make,clean目标还未达成,即会执行clean之后的规则; # 目标 : 依赖 # 规则 .cpp.o: #自动将.cpp识别为源文件后缀,.o识别为目标文件后缀; g++ -c 将.cpp编译成.o ,而不是可执行 @echo "\ncompile..." $(CXX) -c $< -o $@ -DLIBA_API_EXPORTS $(CXXFLAGS) # $@目标文件 $^所有的依赖文件 $<第一个依赖文件 ¥% 仅当目标时函数库文 件,表示规则中的目标成员名; $(TARGET): $(OBJS) # .o到.so .a $(CXX) $^ -o $@ $(CXXFLAGS) $(LDFLAGS) #g++生成动态库 gcc -fPIC -shard -o 目标 源文件 gcc -c -fPIC *.c gcc -shared -o *.so *.o $(AR) -crsv libA.a $^ # ar 创建静态库 -c 禁止再创建库的文件时产生的正常信息 -r 如果已存在,则替换 -s 无论是否修改了库的内容,强制生成库符号表 其余命令可查找学习 install: @echo "\ninstall to output..." cp A.h ADll.h $(OUTPUT)/include # cp 移动 cp -a $(TARGET) ${OUTPUT}/lib clean: @echo "\nclean..." rm -rf $(TARGET) *.o #清除中间文件 copy2B: @echo "\ncopy to libB..." cp ../output/include/* ../../libB/include cp ../output/lib* ../../libB/lib -
现在已经学会了简单的makefile文件编写,接下来就可以进行实践尝试了;
三、动态库的链接
-
将A编译成动态库,B依赖A编译成动态库,main依赖B生成可执行文件,依赖A,B的动态库,
可以通过修改makefile来达到目的。
问题1:
A,B的编译都顺利通过,编译main时依旧顺利通过,但当运行./main时报错:
./main: error while loading shared libraries: libA.so: cannot open shared object file: No such file or directory # 检查makefile里的配置 LDFLAGS = -lA -lB -L../lib/linux -Wl,-rpath=../lib/linux通过ldd ./main可以查看动态库链接信息,可以发现成功链接到libB.so,但是libA.so not found
$ ldd ./main linux-vdso.so.1 (0x00007ffe1d7ff000) libB.so => ../lib/linux/libB.so (0x00007f173646a000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f17360e1000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1735ec9000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1735ad8000) libA.so => not found libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f173573a000) /lib64/ld-linux-x86-64.so.2 (0x00007f1736870000)解决方法:由于在Linux上,可执行程序会去系统默认下的路径去寻找共享库(*.so),因此动态库放在自己的目录下时,编译时会通过,但是执行时却会提示找不到。
-
linux系统默认的动态库路径有 /lib/ /usr/lib 因此在编译动态库时可以直接在makefile里将动态库cp到系统的默认路径下,即可解决问题。
-
如果想自己创建一个用来学习测试的库目录,可以将自己的目录添加到系统的配置文件中,也可以达到目的,方法如下:
sudo vim /etc/ld.so.conf通过vim或gedit打开配置文件,添加自己的目录。
sudo /sbin/ldconfig添加完成之后,保存并通过以上命令加载配置即可。
至于为什么可以找到B却不能找到A,笔者猜测是由于可执行文件先加载B,此时搜索完成指定目录,而B又依赖于A,此时去搜索A的时候,之前的搜索已经完成,因此直接跳过指定目录,转而去搜索系统默认的其他路径。以上只是随意猜测,实际具体的原因笔者并未深入了解;
-
-
如果我们自己生成的B库依赖于第三方的A库,但是不希望主程序知道A库,该怎么办呢?
即将A编译成静态库,在编译B时直接将A完成链接,此时只需要提供B的动态库给主程序即可。我们可以通过修改makefile文件来测试。
-
如果A,B都编译为静态库,同样需要将A,B都交给main。
四、CMake学习
1、以B和main的CmakeLists.txt文件为例子,
#工程的CMakeList.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.0) #最低版本要求
PROJECT(test VERSION 1.0) #项目名称
#编译器
set(CMAKE_C_COMPILER "bin/gcc") #这里没用到交叉编译,可以直接写编译器地址
set(CMAKE_CXX_COMPILER "bin/g++")
#编译选项
set(CMAKE_CXX_FLAGS "-g -Wall -fPIC std=c++11 -02") #编译参数
#libA
ADD_SUBDIRECTORY(libA/src) #添加子目录
#libB
ADD_SUBDIRECTORY(libB/src)
#appC
ADD_SUBDIRECTORY(appC/src)
#libB下的CMakeList.txt
#源文件
AUX_SOURCE_DIRECTORY(. SRC) # 此函数查找目录下所有源文件 = src
#编译动态库
add_definitions("-DLIBB_API_EXPORTS")
#编译静态库,因为B.cpp中include “A.h”,所以要定义LIBA_STATIC
#add_definitions("-DLIBA_STATIC")
#add_definitions("-DLIBB_STATIC")
#头文件目录
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include) # CMAKE_CURRENT_SOURCE_DIR 当前cmakelists所在的目录
#库文件目录
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../lib)
#生成动态库
ADD_LIBRARY(libb_shared SHARED ${SRC}) #用法 ADD_LIBRARY(name [STATIC | SHARED |MODULE] source)
SET_TARGET_PROPERTIES(libb_shared PROPERTIES OUTPUT_NAME "B") #设置目标的一些属性,此处设置了名字
TARGET_LINK_LIBRARIES(libb_shared A) #链接依赖库
#生成静态库
ADD_LIBRARY(libb_static STATIC ${SRC})
SET_TARGET_PROPERTIES(libb_static PROPERTIES OUTPUT_NAME "B")
TARGET_LINK_LIBRARIES(libb_shared A)
set(OUTPUT ${PROJECT_SOURCE_DIR}/libB/output)
INSTALL(FILES B.h BDll.h DESTINATION ${OUTPUT}/include)
INSTALL(TARGETS libb_shared libb_static
LIBRARY DESTINATION ${OUTPUT}/lib)#动态库安装路径
ARCHIVE DESTINATION ${OUTPUT}/lib}#静态库安装路径
## 没用到的命令
##RUNTIME 可执行文件
##public_HEADER 头文件
#main
#源文件
AUX_SOURCE_DIRECTORY(. SRC)
#使用静态库,因为main.cpp中 include"B.h",所以要定义LIBB_STATIC
add_definitions("-DLIBB_STATIC")
#头文件目录
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include)
#库文件目录
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../lib)
#生成可执行文件
ADD_EXECUTABLE(main ${SRC})
TARGET_LINUX_LIBRARIES(main
libB.so
libA.so)
注意在新建build文件夹来进行编译,每一个CMakeLists生成的makefile需要单独进行make编译。
浙公网安备 33010602011771号