在Linux中开发C++

打印 上一主题 下一主题

主题 654|帖子 654|积分 1962

在Linux中开发C++

本文档为本人在学习慕课网课程——[重学C++ ,重构你的C++知识体系]时的一些记录与思考,侵删。学习课程请支持正版!
1. 搭建C/C++编译情况

1.1 gcc 和 g++ 的区别

​ 本质上没有太大区别,gcc 默认使用 c 编译器,g++ 默认使用C++ 编译器:


  • 假如是 .c 文件,gcc 会使用 c 编译器来编译,但 g++ 会使用 c++ 编译器;
  • 但假如是 .cpp 文件,两者是一样的。
1.2 一个C/C++ 小例子

​ 详细代码如下:
  1. #include<iostream>
  2. using namespace std;
  3. int main(int argc,char** argv){
  4.         cout<<"Hello Ubnutu"<<endl;
  5.         return 0;
  6. }
复制代码
​ 可通过以下指令编译:
​ g++ helloworld.cc -o helloworld
​ 即可生成名为 helloworld 的可执行步伐!其中 -o 表现我们指定生成的 可执行文件名称!
​ 但假如我们必要用 gcc 来编译,其会报如下错误:
  1. /usr/bin/ld: /tmp/ccO1YpSf.o: in function `main':
  2. hello.cc:(.text+0x1d): undefined reference to `std::cout'
  3. /usr/bin/ld: hello.cc:(.text+0x22): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
  4. /usr/bin/ld: hello.cc:(.text+0x2c): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)'
  5. /usr/bin/ld: hello.cc:(.text+0x37): undefined reference to `std::ostream::operator<<(std::ostream& (*)(std::ostream&))'
  6. /usr/bin/ld: /tmp/ccO1YpSf.o: in function `__static_initialization_and_destruction_0(int, int)':
  7. hello.cc:(.text+0x6b): undefined reference to `std::ios_base::Init::Init()'
  8. /usr/bin/ld: hello.cc:(.text+0x80): undefined reference to `std::ios_base::Init::~Init()'
  9. collect2: error: ld returned 1 exit status
复制代码
​ 很显然,它没有连接到 std 库,以是例如 std::cout 等指令都没有找到。我们可以改为用如下指令编译:
​ gcc hello.cc -o helloworld -lstdc++
​ 我们发现没有题目了,这是因为 gcc 可以举行 C++ 文件的预处置惩罚,编译,汇编,但不会主动连接 iostream等 C++ 库,而假如我们手动指定必要连接 -lstdc++,它就会去主动连接该库!
​ 参考文档:[C++ OpenCV常见链接error及办理方案,/usr/bin/ld:][https://blog.csdn.net/qq_45983373/article/details/136361499] 中有一幅挺好的图,呈现了C++编译的过程:

​ 除此之外,我们还可以尝试让他显示告诫信息,例如对于一下代码:
  1. #include<iostream>
  2. using namespace std;
  3. int main(int argc,char** argv){
  4.         cout<<"Hello Ubnutu"<<endl;
  5.         int a=0;
  6.         return 0;
  7. }
复制代码
​ 假如我们使用以下指令举行编译:
​ gcc hello.cc -o helloworld -lstdc++ -Wall
​ 我们会发现它就会有如下提示信息:
  1. hello.cc: In function ‘int main(int, char**)’:
  2. hello.cc:5:6: warning: unused variable ‘a’ [-Wunused-variable]
  3.     5 |  int a=0;
  4.       |      ^
复制代码
​ 也就是说,我们使用 -Wall 选项就可以让他输出一些可以优化的提示信息。
1.3 更多编译指令

​ 除此之外,还有如下选项:


  • -ansi : 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,例如 asm 或 typeof 关键字。
  • -S : 知己或预处置惩罚和编译,就是把文件编译成为汇编代码(.s 文件)
  • -c : 质变仪并生成目标文件,即把文件编译为二进制文件(.o 文件)
  • -g : 生成调试信息,GNU 调试器可以使用该信息。
  • -o FILE : 生成指定的输出文件,用在生成可执行文件时。
  • -O0 : 不举行优化处置惩罚
  • -O 或 -O1: 优化生成代码
  • -O2 或 -O3: 进一步优化
  • -shared : 生成共享目标文件。通常用在建立共享库时。
  • -static : 禁止使用共享链接。
  • -w : 不生成任何告诫信息
  • -Wall : 生成所有告诫信息
  • -IDIRECTORY : 指定额外的头文件搜索路径 DICTIONARY
  • -LDIRECTORY : 指定额外的函数库搜索路径 DICTIONARY
  • -ILIBRARY : 连接时搜索指定的函数库 LIBRARY
  • -m486 : 针对 486 举行代码优化。
  • -E 只运行 C 预编译器,即把头文件展开等,生成预编译文件(.i 文件)
​ 最终编译过程描述为图:

2. Makefile 文件的编写

2.1 Makefile 简单介绍

​ makefile 主要在工程实践中帮助我们完成C++工程配置题目。make 字面意思就是制作文件,制作一个当前平台可以运行的文件!像我们之前使用 g++ / gcc 生成可执行文件就是一个简单的 makefile 的过程。
​ 可执行步伐产生过程:


  • 配置情况(体系情况)
  • 确定标准库和头文件位置
  • 确定依赖关系(源代码之间编译的依赖关系)
  • 头文件预编译
  • 预处置惩罚
  • 编译
  • 链接
  • 安装
  • 和操作体系建立接洽
  • 生成安装包
​ 在大型工程中,有许多头文件和 .cpp 文件,它们在编译的时间会存在以来关系!同时许多头文件会被许多文件 include,我们希望只被编译一次!汇编可转化为呆板代码,然后通过链接引入目标库等。
​ 当依赖关系复杂的时间,make 命令工具诞生了,而 Makefile 文件正式为 make 工具所使用的。Makefile 描述了整个工程所有文件的编译顺序、编译规则!属于可执行步伐生成过程中很紧张的部分,帮助我们编译器更好的生成可执行代码。
2.2 多个文件编译简单示例

​ 写一个例子,在reply.h 中定义 Reply 类:
  1. #include<iostream>
  2. class Reply{
  3.         public:
  4.                 Reply();
  5.                 ~Reply();
  6.                 void printHello();
  7. };
复制代码
​ 然后再 reply.cc 中详细定义这三个方法:
  1. #include"reply.h"
  2. using namespace std;
  3. Reply::Reply(){}
  4. Reply::~Reply(){}
  5. void Reply::printHello(){
  6.         cout<<"Helloworld!"<<endl;
  7. }
复制代码
​ 最后我们使用 main 函数调用 Reply:
  1. #include"reply.h"
  2. int main(){
  3.         Reply reply;
  4.         reply.printHello();
  5.         return 0;
  6. }
复制代码
​ 假如我们按照老方法来编译:


  • 使用 gcc main.cc -o main
    报错如下:
    1. /usr/bin/ld: /tmp/cc4EXok8.o: in function `main':
    2. main.cc:(.text+0x24): undefined reference to `Reply::Reply()'
    3. /usr/bin/ld: main.cc:(.text+0x30): undefined reference to `Reply::printHello()'
    4. /usr/bin/ld: main.cc:(.text+0x41): undefined reference to `Reply::~Reply()'
    5. /usr/bin/ld: main.cc:(.text+0x67): undefined reference to `Reply::~Reply()'
    6. /usr/bin/ld: /tmp/cc4EXok8.o: in function `__static_initialization_and_destruction_0(int, int)':
    7. main.cc:(.text+0xab): undefined reference to `std::ios_base::Init::Init()'
    8. /usr/bin/ld: main.cc:(.text+0xc0): undefined reference to `std::ios_base::Init::~Init()'
    9. /usr/bin/ld: /tmp/cc4EXok8.o:(.data.rel.local.DW.ref.__gxx_personality_v0[DW.ref.__gxx_personality_v0]+0x0): undefined reference to `__gxx_personality_v0'
    10. collect2: error: ld returned 1 exit status
    复制代码
  • 使用 gcc mian.cc -o mian -lstdc++
    报错如下:
    1. /usr/bin/ld: /tmp/cchzdnO3.o: in function `main':
    2. main.cc:(.text+0x24): undefined reference to `Reply::Reply()'
    3. /usr/bin/ld: main.cc:(.text+0x30): undefined reference to `Reply::printHello()'
    4. /usr/bin/ld: main.cc:(.text+0x41): undefined reference to `Reply::~Reply()'
    5. /usr/bin/ld: main.cc:(.text+0x67): undefined reference to `Reply::~Reply()'
    6. collect2: error: ld returned 1 exit status
    复制代码
​ 我们发现,按照原来的方式来编译是不能乐成的!但假如我们使用这一句话编译:


  • 使用gcc reply.cc main.cc -o main -lstdc++
​ 是可以乐成编译的!调用 main 也可以乐成输出效果!
​ 那我们尝试使用 make 命令来生成一次!内容如下:
  1. main: reply.o main.o                                                //main 这个文件依赖于 reply.o 和 main.o 两个文件
  2. gcc reply.o main.o -o main -lstdc++                        //生成 main 的指令
  3. reply.o: reply.cc                                                        //上一行是对下一行有依赖关系的
  4. gcc -c reply.cc -o reply.o -lstdc++
  5. main.o: main.cc
  6. gcc -c mian.cc -o main.o -lstdc++
复制代码
​ 然后我们直接执行一个 make 指令,然后就可以一样的生成 main 可执行文件。
2.3 make 和 Makefile 的调用

​ make究竟是什么?
​ make 是操作体系中的一个批处置惩罚工具,它可以帮我们把许多命令融合在一起,一次性把这些命令执行下去,即可以一次性完成许多命令。
​ 可以这样一个比喻,make 是一个指挥家,而 Makefile 则是乐谱,指挥着所有工具完成任务,但为什么 windows 不消那么麻烦呢?
​ 因为 Linux 是相对开放的体系,许多东西必要自己来搭积木,但 windows 是贸易化的,它的用户性是做的比力好的,在 IDE 中许多 maker 的事都帮我们做完了,因此我们必要相识更多细节。
​ 当然在Linux 中,也有其他 IDE 工具,例如 CMake,它就是帮助我们编写 Makefile 的!CMake 我们只必要编写一个 CMakeList.txt,它内部就会帮助我们转换为 Makefile 文件!
​ 还有 QT 中的 QMake,也是实现类似的功能!
2.4 Makefile 的格式

​ 基本语法原则:
  1. target: prerequisites ...
  2.         command ...
复制代码
​ 留意每个命令行前面必须是一个 Tab 字符,即命令行第一个字符是 Tab。
​ 简化规则:
  1. 变量定义: var=string
  2. 变量使用: $(var)
复制代码
​ 此时 Makefile 可以写为:
  1. TARGET = main
  2. OBJS = reply.o main.o
  3. $(TARGET):$(OBJS)
  4.         gcc $(OBJS) -o $(TARGET) -lstdc++
  5. reply.o: reply.cc
  6. main.o: main.cc
复制代码
​ 此时,生成指令如下:
  1. g++    -c -o reply.o reply.cc
  2. g++    -c -o main.o main.cc
  3. gcc reply.o main.o -o main -lstdc++
复制代码
​ 显然,生成 main.o 和 reply.o 的生成都是主动生成的,调用的是 g++。
​ 假如我们希望若已存在则删除?则使用如下指令:
  1. TARGET = main
  2. OBJS = reply.o main.o
  3. $(TARGET):$(OBJS)
  4.         gcc $(OBJS) -o $(TARGET) -lstdc++
  5. reply.o: reply.cc
  6. main.o: main.cc
  7. clean:        rm $(TARGET) $(OBJS)
复制代码
​ 此时,我们可以调用 make clean,即可删除掉这些文件:
  1. rm main reply.o main.o
复制代码
​ 但值得留意的是,这里的 clean 现实上是我们生成的 Target,只不外我们生成的指令是 rm 指令而已;我们可以再优化一下:
  1. TARGET = main
  2. OBJS = reply.o main.o
  3. .PHONY: clean
  4. $(TARGET):$(OBJS)
  5.         gcc $(OBJS) -o $(TARGET) -lstdc++
  6. reply.o: reply.cc
  7. main.o: main.cc
  8. clean:
  9.         rm $(TARGET) $(OBJS)
复制代码
​ 这里的 .PHONY 是一个关键字,代表这个文件并不是实体存在的,因此不会受到已存在 clean 文件的影响!
2.5 用 Makefile 实现步伐安装卸载

​ 如何安装卸载?
  1. TARGET = main
  2. OBJS = reply.o main.o
  3. .PHONY: clean
  4. $(TARGET):$(OBJS)
  5.         gcc $(OBJS) -o $(TARGET) -lstdc++
  6. reply.o: reply.cc
  7. main.o: main.cc
  8. clean:
  9.         rm $(TARGET) $(OBJS)
  10. install:
  11.         cp ./main /usr/local/bin/mainTest
  12. uninstall:
  13.         rm /usr/local/bin/mainTest
复制代码
​ 这样,我们就可以通过 make install 把我们的步伐安装到体系中了,可以在恣意地方通过 mainTest 指令调用!而同样的,可以通过 make uninstall 来卸载。
​ 留意,我们现实上就是把当前可执行文件放到了当前 $PATH 情况变量中,以是我们直接在控制台输入即可访问,当前我的呆板的情况变量:
  1. /home/xuzhenge/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
复制代码
​ 在大型工程中,大概还必要依赖一些第三方库,还必要把一些信息写到注册表中!
2.6 Makefile 的变量题目

​ 在Makefile 中,有以下几种变量:


  • 用户自定义变量:在 Makefile 中,自定义变量很想C++中的宏,但会有一些特别的符号,表现一些特别的含义(后续会提到);且留意,大小写是敏感的!
  • 变量中的变量:
    ​ 在 Makefile 中,支持如下定义方式:
    1. foo = $(bar)
    2. bar = $(ugh)
    3. ugh = Huh
    复制代码
    ​ 也就是我们可以把变量的真实值往后放,把真实的变量延迟到后面,会非常灵活,此时的 bar 就是变量中的变量!
    ​ 当我们使用以下句子时:
    1. test1:
    2.         echo $(foo), foo
    3. test2:
    4.         echo $(bar), bar
    复制代码
    ​ 当我们调用 make test1 时,输出语句为:
    1. Huh, foo
    复制代码
    ​ 也就是前面会输出 foo 变量的最终值,后面会将 foo 作为字符串处置惩罚,输出 foo 字符。
    ​ 这种方法好处是很灵活,但坏处时会让变量的编写过程变得非常繁杂。
    ​ 我们看另一段代码:
    1. foo = $(bar)
    2. bar = $(ugh)
    3. ugh = Huh
    4. test1:        echo $(foo), footest2:        echo $(bar), bary:= $(x)barz=$(x)barx:=footest3:        echo $(x),x        echo $(y),y        echo $(z),z
    复制代码
    ​ 在这一段代码中,最终输出为:
    1. foo,x
    2. bar,y
    3. foobar,z
    复制代码
    ​ 在这里我们可以看到,这里 y 通过 := 赋值,但没有获取到 x 的真实值,这里的 y其实是一个空值;但 z 通过 = 赋值,这里就获取到了 x 的目标值。
    ​ 这里 := 现实上就是制止依赖后面定义的变量的赋值方法!
    ​ 再进一步:
    1. foo = $(bar)
    2. bar = $(ugh)
    3. ugh = Huh
    4. test1:        echo $(foo), footest2:        echo $(bar), bary:= $(x)barz=$(x)barx:=foox+=xzgtest3:        echo $(x),x        echo $(y),y        echo $(z),z
    复制代码
    ​ 其输出为:
    1. foo xzg,x
    2. bar,y
    3. foo xzgbar,z
    复制代码
    ​ 可以看出来,x 在 foo 的基础上还追加了 xzg 字符!
  • 多行变量:
    以下代码:
    1. foo = $(bar)
    2. bar = $(ugh)
    3. ugh = Huh
    4. test1:        echo $(foo), footest2:        echo $(bar), bary:= $(x)barz=$(x)barx:=foox+=xzgtest3:        echo $(x),x        echo $(y),y        echo $(z),zdefine two-linesfooecho $(bar)endeftest4:        echo $(two-lines)
    复制代码
    ​ 其中执行 make test4 效果如下:
    1. foo
    2. Huh
    复制代码
    ​ 即对于一个变量会同时打出两行,第一行即字符 foo,第二行即变量 bar 的最终值,即 Huh。这就是多行变量。
  • 情况变量
    类似于 PATH 这种变量,例如常见的情况变量 $PATH,$LANG 等

    C++ 的情况路径为 $LD_LIBRARY_PATH,即动态和静态库的搜索路径!
    对情况变量赋值可以通过 export 赋值!调用情况变量:
    1. testEnv:
    2.         echo $(HOME),$(SHELL),$(LD_LIBRARY_PATH)
    复制代码
    输出效果为:
    1. /home/xuzhenge,/bin/sh,
    复制代码
    同样的,编译器也有自己的情况变量!
    1. testMakefileEnv:
    2.         echo $(CXX), $(RM)
    复制代码
    其输出效果如下:
    1. g++, rm -f
    复制代码
2.7 实现共享库

​ 在 windows 平台中,共享库一样平常是 .lib 或 .dll 文件;在 Linux 平台中,共享库一样平常是 .so 文件。这里,我们就尝试把刚刚写的 reply.o 文件做成一个共享库 reply.so!
​ 代码如下:
  1. TARGET = main
  2. OBJS = reply.o
  3. LIB = libreply.so
  4. CXXFLAGS = -c -fPIC
  5. .PHONY: clean install uninstall
  6. $(TARGET):$(LIB) main.o
  7.         $(CXX) main.o -o $(TARGET) -L. -lreply
  8. $(LIB):$(OBJS)
  9.         $(CXX) -shared $(OBJS) -o $(LIB)
  10. reply.o: reply.cc
  11.         $(CXX) $(CXXFLAGS) reply.cc -o $(OBJS)
  12. main.o: main.cc
  13.         $(CXX) $(CXXFLAGS) main.cc -o main.o
  14. clean:
  15.         rm $(TARGET) $(OBJS)
  16. install:
  17.         cp ./main /usr/local/bin/mainTest
  18. uninstall:
  19.         rm /usr/local/bin/mainTest
复制代码
​ 首先,我们定义必要生成的文件为 LIB = libreply.so,即一个动态链接库。同时我们必要使用一个 CXXFLAGS 变量来保存 C++ 的编译指令。这里包括两个编译选项,生成目标文件 (-c) 和 生成共享动态链接库 (-fPIC)。
​ 动态链接库可以只生成一份备份,多份代码可共享一份代码!这一份二进制步伐可以被其他二进制步伐所共享!
​ 这里生成过程其实还挺复杂的,其中生成 main 的语句为:
  1. g++ main.o -o main -L. -lreply
复制代码
​ 其中这里的 -L. 表现从当地探求动态链接库,-lreply 表现探求名为 libreply.so 的动态链接库。
​ 生成 main.o 的语句为:
  1. g++ -c -fPIC main.cc -o main.o
复制代码
​ -fPIC选项应用于静态库编译时,编译器会生成额外的位置无关代码和数据结构,以确保生成的静态库可以在差别的内存地址上加载。
​ 生成 libreply.so 的语句如下:
  1. g++ -shared reply.o -o libreply.so
复制代码
​ 这里 -shared 表现生成动态链接库,它是通过 .o 文件生成的!
​ 生成 reply.o 文件的语句如下:
  1. g++ -c -fPIC reply.cc -o reply.o
复制代码
​ 这个语句就没啥好说的了。
​ 但假如直接这样生成,运行 ./main 会出现以下报错:
  1. ./main: error while loading shared libraries: libreply.so: cannot open shared object file: No such file or directory
复制代码
​ 即我们无法打开共享链接库!说明还有一些参数有题目!我们继承修改生成 TARGET 的指令为:
  1. g++ main.o -o main -L. -lreply
  2. -Wl,-rpath ./
复制代码
​ 这里的 -Wl 表现编译器将后面的参数传递个连接器 ld;-rpath 选项添加了一个链接库的定位路径,在运行连接时,会优先搜索 -rpath 的路径,再去搜索 LD_RUN_PATH 的路径。
​ 假如不加 -fPIC,就会有如下报错:
  1. /usr/bin/ld: reply.o: relocation R_X86_64_PC32 against symbol `_ZSt4cout@@GLIBCXX_3.4' can not be used when making a shared object; recompile with -fPIC
  2. /usr/bin/ld: final link failed: bad value
复制代码
​ 其中可以顺利产生 reply.o,但不能产生动态链接库 libreply.so,说明这个 -fPIC 现实上是维持 .o 到 .so 之间的稳固的!
2.8 继承讲变量



  • 主动变量(目标变量):
    把前面的语句写作如下形式:
    1. $(LIB):$(OBJS)
    2.         $(CXX) -shared $^ -o $@
    复制代码
    原本为:
    1. $(LIB):$(OBJS)
    2.         $(CXX) -shared $(OBJS) -o $(LIB)
    复制代码
    这两个变量 $^ 和 $@ 并不是原本就有的,而是依据某些规则生成的变量!公式如下:

    因此这里的 $^ 显着就是 $(OBJS) ,而 $@ 就是 $(LIB) 。留意,这里这两个变量是局部变量,只有在当前规则中有用。因此当变量名冲突的时间则会优先以局部变量为主!
    最后,我们修改完毕的 Makefile 文件长这样:
    1. TARGET = main
    2. OBJS = reply.o
    3. LIB = libreply.so
    4. CXXFLAGS = -c -fPIC
    5. LDFLAGS = -L. -lreply -Wl,-rpath $(@D)
    6. .PHONY: clean install uninstall
    7. $(TARGET):main.o $(LIB)
    8.         $(CXX) $< -o $(TARGET) $(LDFLAGS)
    9. $(LIB):$(OBJS)
    10.         $(CXX) -shared $^ -o $@
    11. reply.o: reply.cc
    12.         $(CXX) $(CXXFLAGS) $< -o $@
    13. main.o: main.cc
    14.         $(CXX) $(CXXFLAGS) $< -o $@
    15. clean:
    16.         rm $(TARGET) $(OBJS) $(LIB) main.o
    17. install:
    18.         cp ./main /usr/local/bin/mainTest
    19. uninstall:
    20.         rm /usr/local/bin/mainTest
    复制代码
  • 模式变量
    不消再依赖项中把每个文件都清晰写出来,可以通过扩展名的方式找到恣意字符为文件名这样的文件,因此有了通配符 %。
    最终简化下来的代码如下:
    1. TARGET = main
    2. OBJS = reply.o
    3. TESTOBJ = main.o
    4. LIB = libreply.so
    5. CXXFLAGS = -c -fPIC
    6. LDFLAGS = -L. -lreply -Wl,-rpath $(@D)
    7. .PHONY: clean install uninstall
    8. $(TARGET):$(TESTOBJ) $(LIB)
    9.         $(CXX) $< -o $(TARGET) $(LDFLAGS)
    10. $(LIB):$(OBJS)
    11.         $(CXX) -shared $^ -o $@
    12. %.o:%.cc
    13.         $(CXX) $(CXXFLAGS) $< -o $@
    14. clean:
    15.         $(RM) $(TARGET) $(OBJS) $(LIB) $(TESTOBJ)
    16. install:
    17.         cp ./main /usr/local/bin/mainTest
    18. uninstall:
    19.         rm /usr/local/bin/mainTest
    复制代码
    ​ 这里就是把 .o 和 .cc 的关系通过一句话都编完了!
3. Makefile 主动生成与部署

​ 在项目中,我们一样平常有如下文件夹


  • src: 源代码,源步伐
  • include: 头文件
  • build: 生成临时文件
  • test: 做测试用例
  • example: 一些 demo
  • Lib: 用于保存第三方库的文件夹
  • bin: 可运行文件
  • ……
​ 但假如是大型项目,直接写 Makefile 文件很累,因此我们有主动化工具:


  • automake/autoconfig
  • CMake
​ 这里就演示一下 CMake 的用法!
​ 先下载安装包,然后用 tar -zxvf 举行解压缩!
​ 然后在解压缩后的文件夹中执行 ./bootstrap 命令即可安装。
​ 然后我们就可以编写 CMakeList.txt,这个是有固定模板的!
  1. #CMakeLists.txt
  2. # 设置 cmake 最低版本
  3. cmake_minimum_required(VERSION 2.8.0)
  4. # 设置C++标准
  5. set(CMAKE_CXX_STANDARD 11)
  6. # 项目名称
  7. project(cmake_test)
  8. # 包含的头文件目录
  9. include_directories(./include)
  10. set(SRC_DIR ./src)
  11. # 指定生成链接库
  12. add_library(XXX ${SRC_DIR}/XXX.cc)
  13. add_library(YYY ${SRC_DIR}/YYY.cc)
  14. # 设置变量
  15. set(LIBRARIES XXX YYY)
  16. set(OBJECT XXX_test)
  17. # 生成可执行文件
  18. add_executable(${OBJECT} ${SRC_DIR}/main.cc)
  19. # 为可执行文件链接目标库
  20. target_link_libraries(${OBJECT} ${LIBRARIES})
复制代码
​ 生成的 Makefile 文件如下:
  1. # CMAKE generated file: DO NOT EDIT!
  2. # Generated by "Unix Makefiles" Generator, CMake Version 3.16
  3. # Default target executed when no arguments are given to make.
  4. default_target: all
  5. .PHONY : default_target
  6. # Allow only one "make -f Makefile2" at a time, but pass parallelism.
  7. .NOTPARALLEL:
  8. #=============================================================================
  9. # Special targets provided by cmake.
  10. # Disable implicit rules so canonical targets will work.
  11. .SUFFIXES:
  12. # Remove some rules from gmake that .SUFFIXES does not remove.
  13. SUFFIXES =
  14. .SUFFIXES: .hpux_make_needs_suffix_list
  15. # Suppress display of executed commands.
  16. $(VERBOSE).SILENT:
  17. # A target that is always out of date.
  18. cmake_force:
  19. .PHONY : cmake_force
  20. #=============================================================================
  21. # Set environment variables for the build.
  22. # The shell in which to execute make rules.
  23. SHELL = /bin/sh
  24. # The CMake executable.
  25. CMAKE_COMMAND = /usr/bin/cmake
  26. # The command to remove a file.
  27. RM = /usr/bin/cmake -E remove -f
  28. # Escaping for special characters.
  29. EQUALS = =
  30. # The top-level source directory on which CMake was run.
  31. CMAKE_SOURCE_DIR = /home/xuzhenge/Desktop/testCPP/TestMake
  32. # The top-level build directory on which CMake was run.
  33. CMAKE_BINARY_DIR = /home/xuzhenge/Desktop/testCPP/TestMake/build
  34. #=============================================================================
  35. # Targets provided globally by CMake.
  36. # Special rule for the target rebuild_cache
  37. rebuild_cache:
  38.         @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..."
  39.         /usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
  40. .PHONY : rebuild_cache
  41. # Special rule for the target rebuild_cache
  42. rebuild_cache/fast: rebuild_cache
  43. .PHONY : rebuild_cache/fast
  44. # Special rule for the target edit_cache
  45. edit_cache:
  46.         @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..."
  47.         /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available.
  48. .PHONY : edit_cache
  49. # Special rule for the target edit_cache
  50. edit_cache/fast: edit_cache
  51. .PHONY : edit_cache/fast
  52. # The main all target
  53. all: cmake_check_build_system
  54.         $(CMAKE_COMMAND) -E cmake_progress_start /home/xuzhenge/Desktop/testCPP/TestMake/build/CMakeFiles /home/xuzhenge/Desktop/testCPP/TestMake/build/CMakeFiles/progress.marks
  55.         $(MAKE) -f CMakeFiles/Makefile2 all
  56.         $(CMAKE_COMMAND) -E cmake_progress_start /home/xuzhenge/Desktop/testCPP/TestMake/build/CMakeFiles 0
  57. .PHONY : all
  58. # The main clean target
  59. clean:
  60.         $(MAKE) -f CMakeFiles/Makefile2 clean
  61. .PHONY : clean
  62. # The main clean target
  63. clean/fast: clean
  64. .PHONY : clean/fast
  65. # Prepare targets for installation.
  66. preinstall: all
  67.         $(MAKE) -f CMakeFiles/Makefile2 preinstall
  68. .PHONY : preinstall
  69. # Prepare targets for installation.
  70. preinstall/fast:
  71.         $(MAKE) -f CMakeFiles/Makefile2 preinstall
  72. .PHONY : preinstall/fast
  73. # clear depends
  74. depend:
  75.         $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
  76. .PHONY : depend
  77. #=============================================================================
  78. # Target rules for targets named main_test
  79. # Build rule for target.
  80. main_test: cmake_check_build_system
  81.         $(MAKE) -f CMakeFiles/Makefile2 main_test
  82. .PHONY : main_test
  83. # fast build rule for target.
  84. main_test/fast:
  85.         $(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/build
  86. .PHONY : main_test/fast
  87. #=============================================================================
  88. # Target rules for targets named main
  89. # Build rule for target.
  90. main: cmake_check_build_system
  91.         $(MAKE) -f CMakeFiles/Makefile2 main
  92. .PHONY : main
  93. # fast build rule for target.
  94. main/fast:
  95.         $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/build
  96. .PHONY : main/fast
  97. #=============================================================================
  98. # Target rules for targets named reply
  99. # Build rule for target.
  100. reply: cmake_check_build_system
  101.         $(MAKE) -f CMakeFiles/Makefile2 reply
  102. .PHONY : reply
  103. # fast build rule for target.
  104. reply/fast:
  105.         $(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/build
  106. .PHONY : reply/fast
  107. src/main.o: src/main.cc.o
  108. .PHONY : src/main.o
  109. # target to build an object file
  110. src/main.cc.o:
  111.         $(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/src/main.cc.o
  112.         $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/src/main.cc.o
  113. .PHONY : src/main.cc.o
  114. src/main.i: src/main.cc.i
  115. .PHONY : src/main.i
  116. # target to preprocess a source file
  117. src/main.cc.i:
  118.         $(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/src/main.cc.i
  119.         $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/src/main.cc.i
  120. .PHONY : src/main.cc.i
  121. src/main.s: src/main.cc.s
  122. .PHONY : src/main.s
  123. # target to generate assembly for a file
  124. src/main.cc.s:
  125.         $(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/src/main.cc.s
  126.         $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/src/main.cc.s
  127. .PHONY : src/main.cc.s
  128. src/reply.o: src/reply.cc.o
  129. .PHONY : src/reply.o
  130. # target to build an object file
  131. src/reply.cc.o:
  132.         $(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/src/reply.cc.o
  133. .PHONY : src/reply.cc.o
  134. src/reply.i: src/reply.cc.i
  135. .PHONY : src/reply.i
  136. # target to preprocess a source file
  137. src/reply.cc.i:
  138.         $(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/src/reply.cc.i
  139. .PHONY : src/reply.cc.i
  140. src/reply.s: src/reply.cc.s
  141. .PHONY : src/reply.s
  142. # target to generate assembly for a file
  143. src/reply.cc.s:
  144.         $(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/src/reply.cc.s
  145. .PHONY : src/reply.cc.s
  146. # Help Target
  147. help:
  148.         @echo "The following are some of the valid targets for this Makefile:"
  149.         @echo "... all (the default if no target is provided)"
  150.         @echo "... clean"
  151.         @echo "... depend"
  152.         @echo "... rebuild_cache"
  153.         @echo "... main_test"
  154.         @echo "... main"
  155.         @echo "... edit_cache"
  156.         @echo "... reply"
  157.         @echo "... src/main.o"
  158.         @echo "... src/main.i"
  159.         @echo "... src/main.s"
  160.         @echo "... src/reply.o"
  161.         @echo "... src/reply.i"
  162.         @echo "... src/reply.s"
  163. .PHONY : help
  164. #=============================================================================
  165. # Special targets to cleanup operation of make.
  166. # Special rule to run CMake to check the build system integrity.
  167. # No rule that depends on this can have commands that come from listfiles
  168. # because they might be regenerated.
  169. cmake_check_build_system:
  170.         $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
  171. .PHONY : cmake_check_build_system
复制代码
4. 如何调用他人写的库

4.1 通过 -L. 指定库目录编译

​ 我们以 openfec 为例,这是一个由 www.openfec.org 提供的用于方便举行 AL-FEC 编解码的方法,经过 make 后,它会生成一个 libopenfec.so 文件,下面,我们将要尝试假如在其他 .cc 代码中引入该文件的相关方法应该如何编译!
​ 我们编译的目标是 openfec 自带的一个例子,详细在 ~/applis/how_to_examples/simple_client_server 下,包含头文件 simple_client_server.h 和两个代码文件 simple_server.c 、 simple_client.c 。显然,我们必要编译的文件就是后两个代码文件。当前文件结构如下图所示:

​ 当然,我们看到这个目录下其实也是有 CMakeLists.txt 的,说明我们同样可以通过 cmake 的方式生成 Makefile 文件,然后使用 make 举行编译。打开 CMakeLists.txt 就可以看到,它在内里也声明白 openfec 的库文件(target_link_libraries):

​ 然而,在这里我们偏不消 make 的方式来编译,而是直接使用 gcc 来编译,领会一下 C++ 代码编译的过程。显然,我们必要声明的库文件即 libopenfec.so,那么这个库在哪呢?
​ 在 openfec 官网中曾提到,可以使用 make 指令来编译 openfec 的源代码,编译效果将会放到 ~/bin/Debug 文件夹下,这个文件夹的内容如下图所示:

​ 可以看到,这里的 libopenfec.so 就是我们想要的文件。当然除了这个文件亦以外,还有一个 libopenfec.so.1,这个文件现实上是运行时的连接文件,也就是 -Wl,-rpath 指定的目标文件,后续可以通过报错看到他们之间的关系。
​ 首先,我们把 libopenfec.so 文件挪到 openfec 提供的例子目录(~/applis/how_to_examples/ simple_client_server)下,此时该目录内容如下:

​ 此时,调用我们的编译语句:
​ gcc simple_server.c -o simple_server -L. -lopenfec
​ 可以看到,代码顺利生成了 simple_server 的可执行文件,但是我们现实运行时,它会报如下错误:

​ 显然,它没有找到库 libopenfec.so.1 ,那么我们把该目录也放到当前文件夹下,并通过如下指令举行编译:
​ gcc simple_server.c -o simple_server -L. -lopenfec -Wl,-rpath ./
​ 此时,生成的可执行文件不再堕落,可以正常运行!
4.2 放到默认库目录文件夹下编译

​ 在前面的例子中,我们通过 -L. 指定了库文件的目录,但这似乎不太符合我们一样平常的使用习惯,一样平常我们指定了 -lopenfec 之后就可以直接编译了,在本小节中,我们进一步尝试直接把 .so 文件放到默认库目录文件夹下编译!
​ 在 C++ 编译中,它会按照以下顺序来找库文件位置:

​ 留意,这里说的仅仅是 编译过程中的库文件,而不包含运行过程中的库文件。在C++中,编译过程中要求的库文件叫静态库(例如上面的 libopenfec.so),而在运行过程中要求的库文件叫动态库(例如上面的 libopenfec.so.1)。在编译时指定库文件,静态库通过 -L 指定,而动态库则通过 -Wl,-rpath 指定;同样的对于情况变量,静态库通过 $LIBRARY_PATH 指定,而动态库则通过 $LD_LIBRARY_PATH 指定!下面,我们希望把我们必要的 openfec.so 和 openfec.so.1 放到桌面的目录 ~/Desktop/openFECLib 中,然后我们通过情况变量指定这个目录作为库目录之一,使我们编译时不再必要额外指定库目录路径!
​ 此时,~/Desktop/openFECLib 的内容如下:

​ 现实上就是把库文件(静态库和动态库)都放到了该目录下,然后就很简单,就向情况变量设置该目录即可:
​ export LIBRARY_PATH=~/Desktop/openFECLib/
​ export LD_LIBRARY_PATH=~/Desktop/openFECLib/
​ 必要留意的是,我这里是直接重写了该变量,假如只是希望往内里添加新的路径,则通过如下指令:
​ export LIBRARY_PATH=~/Desktop/openFECLib/LIBRARY_PATH
​ export LD_LIBRARY_PATH=~/Desktop/openFECLib/LD_LIBRARY_PATH
​ 此时,我们重新对目标文件举行编译,不外我们执行如下指令:
​ gcc simple_server.c -o simple_server -lopenfec
​ 很好,没有报错,直接就编译乐成了,而且生成的 simple_server 是可以直接运行的!
​ 但是,这还有一个缺陷,我们设置的情况变量是临时变量,我们把当前终端关掉了该变量就丢失了… 那么我们有三种选择:


  • 每次 gcc 之前先设置 LIBRARY_PATH 和 LD_LIBRARY_PATH 的路径。
  • 设置一个永久的变量
  • 把库文件放到默认目录中:/usr/local/lib,就像我们安装应用那样(必要sudo)。
​ 第一种和第三种方法就不多说了,我们试试第二种方法,设置全局情况变量,首先执行指令:
​ vim ~/.bashrc
​ 打开文件后,在末尾处直接添加:
​ export LIBRARY_PATH=~/Desktop/openFECLib/
​ export LD_LIBRARY_PATH=~/Desktop/openFECLib/
​ 保存退出后执行 source .bashrc 令其见效即可!
​ 这样我们就乐成设置了一个持久的变量!
​ 本文档完~
到桌面的目录 ~/Desktop/openFECLib 中,然后我们通过情况变量指定这个目录作为库目录之一,使我们编译时不再必要额外指定库目录路径!
​ 此时,~/Desktop/openFECLib 的内容如下:
[外链图片转存中…(img-rUhDcfVG-1710836785819)]
​ 现实上就是把库文件(静态库和动态库)都放到了该目录下,然后就很简单,就向情况变量设置该目录即可:
​ export LIBRARY_PATH=~/Desktop/openFECLib/
​ export LD_LIBRARY_PATH=~/Desktop/openFECLib/
​ 必要留意的是,我这里是直接重写了该变量,假如只是希望往内里添加新的路径,则通过如下指令:
​ export LIBRARY_PATH=~/Desktop/openFECLib/LIBRARY_PATH
​ export LD_LIBRARY_PATH=~/Desktop/openFECLib/LD_LIBRARY_PATH
​ 此时,我们重新对目标文件举行编译,不外我们执行如下指令:
​ gcc simple_server.c -o simple_server -lopenfec
​ 很好,没有报错,直接就编译乐成了,而且生成的 simple_server 是可以直接运行的!
​ 但是,这还有一个缺陷,我们设置的情况变量是临时变量,我们把当前终端关掉了该变量就丢失了… 那么我们有三种选择:


  • 每次 gcc 之前先设置 LIBRARY_PATH 和 LD_LIBRARY_PATH 的路径。
  • 设置一个永久的变量
  • 把库文件放到默认目录中:/usr/local/lib,就像我们安装应用那样(必要sudo)。
​ 第一种和第三种方法就不多说了,我们试试第二种方法,设置全局情况变量,首先执行指令:
​ vim ~/.bashrc
​ 打开文件后,在末尾处直接添加:
​ export LIBRARY_PATH=~/Desktop/openFECLib/
​ export LD_LIBRARY_PATH=~/Desktop/openFECLib/
​ 保存退出后执行 source .bashrc 令其见效即可!
​ 这样我们就乐成设置了一个持久的变量!
​ 本文档完~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

河曲智叟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表