当前位置: 首页 > news >正文

在Linux中开发C++

在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++ 小例子

​ 具体代码如下:

#include<iostream>
using namespace std;
int main(int argc,char** argv){cout<<"Hello Ubnutu"<<endl;return 0;
}

​ 可通过以下指令编译:

g++ helloworld.cc -o helloworld

​ 即可生成名为 helloworld 的可执行程序!其中 -o 表示我们指定生成的 可执行文件名称!

​ 但如果我们需要用 gcc 来编译,其会报如下错误:

/usr/bin/ld: /tmp/ccO1YpSf.o: in function `main':
hello.cc:(.text+0x1d): undefined reference to `std::cout'
/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*)'
/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> >&)'
/usr/bin/ld: hello.cc:(.text+0x37): undefined reference to `std::ostream::operator<<(std::ostream& (*)(std::ostream&))'
/usr/bin/ld: /tmp/ccO1YpSf.o: in function `__static_initialization_and_destruction_0(int, int)':
hello.cc:(.text+0x6b): undefined reference to `std::ios_base::Init::Init()'
/usr/bin/ld: hello.cc:(.text+0x80): undefined reference to `std::ios_base::Init::~Init()'
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++编译的过程:

在这里插入图片描述

​ 除此之外,我们还可以尝试让他显示警告信息,例如对于一下代码:

#include<iostream>
using namespace std;
int main(int argc,char** argv){cout<<"Hello Ubnutu"<<endl;int a=0;return 0;
}

​ 如果我们使用以下指令进行编译:

gcc hello.cc -o helloworld -lstdc++ -Wall

​ 我们会发现它就会有如下提示信息:

hello.cc: In function ‘int main(int, char**)’:
hello.cc:5:6: warning: unused variable ‘a’ [-Wunused-variable]5 |  int a=0;|      ^

​ 也就是说,我们使用 -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 类:

#include<iostream>
class Reply{public:Reply();~Reply();void printHello();
};

​ 然后再 reply.cc 中具体定义这三个方法:

#include"reply.h"
using namespace std;
Reply::Reply(){}
Reply::~Reply(){}
void Reply::printHello(){cout<<"Helloworld!"<<endl;
}

​ 最后我们使用 main 函数调用 Reply:

#include"reply.h"
int main(){Reply reply;reply.printHello();return 0;
}

​ 如果我们按照老方法来编译:

  • 使用 gcc main.cc -o main

    报错如下:

    /usr/bin/ld: /tmp/cc4EXok8.o: in function `main':
    main.cc:(.text+0x24): undefined reference to `Reply::Reply()'
    /usr/bin/ld: main.cc:(.text+0x30): undefined reference to `Reply::printHello()'
    /usr/bin/ld: main.cc:(.text+0x41): undefined reference to `Reply::~Reply()'
    /usr/bin/ld: main.cc:(.text+0x67): undefined reference to `Reply::~Reply()'
    /usr/bin/ld: /tmp/cc4EXok8.o: in function `__static_initialization_and_destruction_0(int, int)':
    main.cc:(.text+0xab): undefined reference to `std::ios_base::Init::Init()'
    /usr/bin/ld: main.cc:(.text+0xc0): undefined reference to `std::ios_base::Init::~Init()'
    /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'
    collect2: error: ld returned 1 exit status
    
  • 使用 gcc mian.cc -o mian -lstdc++

    报错如下:

    /usr/bin/ld: /tmp/cchzdnO3.o: in function `main':
    main.cc:(.text+0x24): undefined reference to `Reply::Reply()'
    /usr/bin/ld: main.cc:(.text+0x30): undefined reference to `Reply::printHello()'
    /usr/bin/ld: main.cc:(.text+0x41): undefined reference to `Reply::~Reply()'
    /usr/bin/ld: main.cc:(.text+0x67): undefined reference to `Reply::~Reply()'
    collect2: error: ld returned 1 exit status
    

​ 我们发现,按照原来的方式来编译是不能成功的!但如果我们使用这一句话编译:

  • 使用gcc reply.cc main.cc -o main -lstdc++

​ 是可以成功编译的!调用 main 也可以成功输出结果!

​ 那我们尝试使用 make 命令来生成一次!内容如下:

main: reply.o main.o						//main 这个文件依赖于 reply.o 和 main.o 两个文件
gcc reply.o main.o -o main -lstdc++			//生成 main 的指令
reply.o: reply.cc							//上一行是对下一行有依赖关系的
gcc -c reply.cc -o reply.o -lstdc++
main.o: main.cc
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 的格式

​ 基本语法原则:

target: prerequisites ...command ...

​ 注意每个命令行前面必须是一个 Tab 字符,即命令行第一个字符是 Tab。

​ 简化规则:

变量定义: var=string
变量使用: $(var)

​ 此时 Makefile 可以写为:

TARGET = main
OBJS = reply.o main.o
$(TARGET):$(OBJS)gcc $(OBJS) -o $(TARGET) -lstdc++
reply.o: reply.cc
main.o: main.cc

​ 此时,生成指令如下:

g++    -c -o reply.o reply.cc
g++    -c -o main.o main.cc
gcc reply.o main.o -o main -lstdc++

​ 显然,生成 main.o 和 reply.o 的生成都是自动生成的,调用的是 g++。

​ 如果我们希望若已存在则删除?则使用如下指令:

TARGET = main
OBJS = reply.o main.o
$(TARGET):$(OBJS)gcc $(OBJS) -o $(TARGET) -lstdc++
reply.o: reply.cc
main.o: main.ccclean:rm $(TARGET) $(OBJS)

​ 此时,我们可以调用 make clean,即可删除掉这些文件:

rm main reply.o main.o

​ 但值得注意的是,这里的 clean 实际上是我们生成的 Target,只不过我们生成的指令是 rm 指令而已;我们可以再优化一下:

TARGET = main
OBJS = reply.o main.o.PHONY: clean$(TARGET):$(OBJS)gcc $(OBJS) -o $(TARGET) -lstdc++
reply.o: reply.cc
main.o: main.ccclean:rm $(TARGET) $(OBJS)

​ 这里的 .PHONY 是一个关键字,代表这个文件并不是实体存在的,因此不会受到已存在 clean 文件的影响!

2.5 用 Makefile 实现程序安装卸载

​ 如何安装卸载?

TARGET = main
OBJS = reply.o main.o.PHONY: clean$(TARGET):$(OBJS)gcc $(OBJS) -o $(TARGET) -lstdc++
reply.o: reply.cc
main.o: main.ccclean:rm $(TARGET) $(OBJS)install:cp ./main /usr/local/bin/mainTest
uninstall:rm /usr/local/bin/mainTest

​ 这样,我们就可以通过 make install 把我们的程序安装到系统中了,可以在任意地方通过 mainTest 指令调用!而同样的,可以通过 make uninstall 来卸载。

​ 注意,我们实际上就是把当前可执行文件放到了当前 $PATH 环境变量中,所以我们直接在控制台输入即可访问,当前我的机器的环境变量:

/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 中,支持如下定义方式:

    foo = $(bar)
    bar = $(ugh)
    ugh = Huh
    

    ​ 也就是我们可以把变量的真实值往后放,把真实的变量延迟到后面,会非常灵活,此时的 bar 就是变量中的变量!

    ​ 当我们使用以下句子时:

    test1:echo $(foo), foo
    test2:echo $(bar), bar
    

    ​ 当我们调用 make test1 时,输出语句为:

    Huh, foo
    

    ​ 也就是前面会输出 foo 变量的最终值,后面会将 foo 作为字符串处理,输出 foo 字符。

    ​ 这种方法好处是很灵活,但坏处时会让变量的编写过程变得非常繁杂。

    ​ 我们看另一段代码:

    foo = $(bar)
    bar = $(ugh)
    ugh = Huhtest1:echo $(foo), foo
    test2:echo $(bar), bary:= $(x)bar
    z=$(x)bar
    x:=foo
    test3:echo $(x),xecho $(y),yecho $(z),z
    

    ​ 在这一段代码中,最终输出为:

    foo,x
    bar,y
    foobar,z
    

    ​ 在这里我们可以看到,这里 y 通过 := 赋值,但没有获取到 x 的真实值,这里的 y其实是一个空值;但 z 通过 = 赋值,这里就获取到了 x 的目标值。

    ​ 这里 := 实际上就是避免依赖后面定义的变量的赋值方法!

    ​ 再进一步:

    foo = $(bar)
    bar = $(ugh)
    ugh = Huhtest1:echo $(foo), foo
    test2:echo $(bar), bary:= $(x)bar
    z=$(x)bar
    x:=foo
    x+=xzg
    test3:echo $(x),xecho $(y),yecho $(z),z
    

    ​ 其输出为:

    foo xzg,x
    bar,y
    foo xzgbar,z
    

    ​ 可以看出来,x 在 foo 的基础上还追加了 xzg 字符!

  • 多行变量:

    以下代码:

    foo = $(bar)
    bar = $(ugh)
    ugh = Huhtest1:echo $(foo), foo
    test2:echo $(bar), bary:= $(x)bar
    z=$(x)bar
    x:=foo
    x+=xzg
    test3:echo $(x),xecho $(y),yecho $(z),z
    define two-lines
    foo
    echo $(bar)
    endef
    test4:echo $(two-lines)
    

    ​ 其中执行 make test4 结果如下:

    foo
    Huh
    

    ​ 即对于一个变量会同时打出两行,第一行即字符 foo,第二行即变量 bar 的最终值,即 Huh。这就是多行变量。

  • 环境变量

    类似于 PATH 这种变量,例如常见的环境变量 $PATH,$LANG 等

    在这里插入图片描述

    C++ 的环境路径为 $LD_LIBRARY_PATH,即动态和静态库的搜索路径!

    对环境变量赋值可以通过 export 赋值!调用环境变量:

    testEnv:echo $(HOME),$(SHELL),$(LD_LIBRARY_PATH)
    

    输出结果为:

    /home/xuzhenge,/bin/sh,
    

    同样的,编译器也有自己的环境变量!

    testMakefileEnv:echo $(CXX), $(RM)
    

    其输出结果如下:

    g++, rm -f
    
2.7 实现共享库

​ 在 windows 平台中,共享库一般是 .lib 或 .dll 文件;在 Linux 平台中,共享库一般是 .so 文件。这里,我们就尝试把刚刚写的 reply.o 文件做成一个共享库 reply.so!

​ 代码如下:

TARGET = main
OBJS = reply.o
LIB = libreply.so
CXXFLAGS = -c -fPIC.PHONY: clean install uninstall$(TARGET):$(LIB) main.o$(CXX) main.o -o $(TARGET) -L. -lreply
$(LIB):$(OBJS)$(CXX) -shared $(OBJS) -o $(LIB)
reply.o: reply.cc$(CXX) $(CXXFLAGS) reply.cc -o $(OBJS)
main.o: main.cc$(CXX) $(CXXFLAGS) main.cc -o main.oclean:rm $(TARGET) $(OBJS)install:cp ./main /usr/local/bin/mainTest
uninstall:rm /usr/local/bin/mainTest

​ 首先,我们定义需要生成的文件为 LIB = libreply.so,即一个动态链接库。同时我们需要使用一个 CXXFLAGS 变量来保存 C++ 的编译指令。这里包括两个编译选项,生成目标文件 (-c) 和 生成共享动态链接库 (-fPIC)。

​ 动态链接库可以只生成一份备份,多份代码可共享一份代码!这一份二进制程序可以被其他二进制程序所共享!

​ 这里生成过程其实还挺复杂的,其中生成 main 的语句为:

g++ main.o -o main -L. -lreply

​ 其中这里的 -L. 表示从本地寻找动态链接库,-lreply 表示寻找名为 libreply.so 的动态链接库。

​ 生成 main.o 的语句为:

g++ -c -fPIC main.cc -o main.o

-fPIC选项应用于静态库编译时,编译器会生成额外的位置无关代码和数据布局,以确保生成的静态库可以在不同的内存地址上加载。

​ 生成 libreply.so 的语句如下:

g++ -shared reply.o -o libreply.so

​ 这里 -shared 表示生成动态链接库,它是通过 .o 文件生成的!

​ 生成 reply.o 文件的语句如下:

g++ -c -fPIC reply.cc -o reply.o

​ 这个语句就没啥好说的了。

​ 但如果直接这样生成,运行 ./main 会出现以下报错:

./main: error while loading shared libraries: libreply.so: cannot open shared object file: No such file or directory

​ 即我们无法打开共享链接库!说明还有一些参数有问题!我们继续修改生成 TARGET 的指令为:

g++ main.o -o main -L. -lreply -Wl,-rpath ./

​ 这里的 -Wl 表示编译器将后面的参数传递个连接器 ld;-rpath 选项添加了一个链接库的定位路径,在运行连接时,会优先搜索 -rpath 的路径,再去搜索 LD_RUN_PATH 的路径。

​ 如果不加 -fPIC,就会有如下报错:

/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
/usr/bin/ld: final link failed: bad value

​ 其中可以顺利产生 reply.o,但不能产生动态链接库 libreply.so,说明这个 -fPIC 实际上是维持 .o 到 .so 之间的稳定的!

2.8 继续讲变量
  • 自动变量(目标变量):

    把前面的语句写作如下形式:

    $(LIB):$(OBJS)$(CXX) -shared $^ -o $@
    

    原本为:

    $(LIB):$(OBJS)$(CXX) -shared $(OBJS) -o $(LIB)
    

    这两个变量 $^ 和 $@ 并不是原本就有的,而是依据某些规则生成的变量!公式如下:

    在这里插入图片描述

    因此这里的 $^ 明显就是 $(OBJS) ,而 $@ 就是 $(LIB) 。注意,这里这两个变量是局部变量,只有在当前规则中有效。因此当变量名冲突的时候则会优先以局部变量为主!

    最后,我们修改完毕的 Makefile 文件长这样:

    TARGET = main
    OBJS = reply.o
    LIB = libreply.so
    CXXFLAGS = -c -fPIC
    LDFLAGS = -L. -lreply -Wl,-rpath $(@D).PHONY: clean install uninstall$(TARGET):main.o $(LIB)$(CXX) $< -o $(TARGET) $(LDFLAGS)
    $(LIB):$(OBJS)$(CXX) -shared $^ -o $@
    reply.o: reply.cc$(CXX) $(CXXFLAGS) $< -o $@
    main.o: main.cc$(CXX) $(CXXFLAGS) $< -o $@clean:rm $(TARGET) $(OBJS) $(LIB) main.oinstall:cp ./main /usr/local/bin/mainTest
    uninstall:rm /usr/local/bin/mainTest
    
  • 模式变量

    不用再依赖项中把每个文件都清晰写出来,可以通过扩展名的方式找到任意字符为文件名这样的文件,因此有了通配符 %。

    最终简化下来的代码如下:

    TARGET = main
    OBJS = reply.o
    TESTOBJ = main.o
    LIB = libreply.so
    CXXFLAGS = -c -fPIC
    LDFLAGS = -L. -lreply -Wl,-rpath $(@D).PHONY: clean install uninstall$(TARGET):$(TESTOBJ) $(LIB)$(CXX) $< -o $(TARGET) $(LDFLAGS)
    $(LIB):$(OBJS)$(CXX) -shared $^ -o $@
    %.o:%.cc$(CXX) $(CXXFLAGS) $< -o $@clean:$(RM) $(TARGET) $(OBJS) $(LIB) $(TESTOBJ)install:cp ./main /usr/local/bin/mainTest
    uninstall: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,这个是有固定模板的!

#CMakeLists.txt
# 设置 cmake 最低版本
cmake_minimum_required(VERSION 2.8.0)
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
# 项目名称
project(cmake_test)
# 包含的头文件目录
include_directories(./include)
set(SRC_DIR ./src)
# 指定生成链接库
add_library(XXX ${SRC_DIR}/XXX.cc)
add_library(YYY ${SRC_DIR}/YYY.cc)
# 设置变量
set(LIBRARIES XXX YYY)
set(OBJECT XXX_test)
# 生成可执行文件
add_executable(${OBJECT} ${SRC_DIR}/main.cc)
# 为可执行文件链接目标库
target_link_libraries(${OBJECT} ${LIBRARIES})

​ 生成的 Makefile 文件如下:

# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.16# Default target executed when no arguments are given to make.
default_target: all.PHONY : default_target# Allow only one "make -f Makefile2" at a time, but pass parallelism.
.NOTPARALLEL:#=============================================================================
# Special targets provided by cmake.# Disable implicit rules so canonical targets will work.
.SUFFIXES:# Remove some rules from gmake that .SUFFIXES does not remove.
SUFFIXES =.SUFFIXES: .hpux_make_needs_suffix_list# Suppress display of executed commands.
$(VERBOSE).SILENT:# A target that is always out of date.
cmake_force:.PHONY : cmake_force#=============================================================================
# Set environment variables for the build.# The shell in which to execute make rules.
SHELL = /bin/sh# The CMake executable.
CMAKE_COMMAND = /usr/bin/cmake# The command to remove a file.
RM = /usr/bin/cmake -E remove -f# Escaping for special characters.
EQUALS = =# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/xuzhenge/Desktop/testCPP/TestMake# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/xuzhenge/Desktop/testCPP/TestMake/build#=============================================================================
# Targets provided globally by CMake.# Special rule for the target rebuild_cache
rebuild_cache:@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..."/usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : rebuild_cache# Special rule for the target rebuild_cache
rebuild_cache/fast: rebuild_cache.PHONY : rebuild_cache/fast# Special rule for the target edit_cache
edit_cache:@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..."/usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available.
.PHONY : edit_cache# Special rule for the target edit_cache
edit_cache/fast: edit_cache.PHONY : edit_cache/fast# The main all target
all: cmake_check_build_system$(CMAKE_COMMAND) -E cmake_progress_start /home/xuzhenge/Desktop/testCPP/TestMake/build/CMakeFiles /home/xuzhenge/Desktop/testCPP/TestMake/build/CMakeFiles/progress.marks$(MAKE) -f CMakeFiles/Makefile2 all$(CMAKE_COMMAND) -E cmake_progress_start /home/xuzhenge/Desktop/testCPP/TestMake/build/CMakeFiles 0
.PHONY : all# The main clean target
clean:$(MAKE) -f CMakeFiles/Makefile2 clean
.PHONY : clean# The main clean target
clean/fast: clean.PHONY : clean/fast# Prepare targets for installation.
preinstall: all$(MAKE) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall# Prepare targets for installation.
preinstall/fast:$(MAKE) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall/fast# clear depends
depend:$(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
.PHONY : depend#=============================================================================
# Target rules for targets named main_test# Build rule for target.
main_test: cmake_check_build_system$(MAKE) -f CMakeFiles/Makefile2 main_test
.PHONY : main_test# fast build rule for target.
main_test/fast:$(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/build
.PHONY : main_test/fast#=============================================================================
# Target rules for targets named main# Build rule for target.
main: cmake_check_build_system$(MAKE) -f CMakeFiles/Makefile2 main
.PHONY : main# fast build rule for target.
main/fast:$(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/build
.PHONY : main/fast#=============================================================================
# Target rules for targets named reply# Build rule for target.
reply: cmake_check_build_system$(MAKE) -f CMakeFiles/Makefile2 reply
.PHONY : reply# fast build rule for target.
reply/fast:$(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/build
.PHONY : reply/fastsrc/main.o: src/main.cc.o.PHONY : src/main.o# target to build an object file
src/main.cc.o:$(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/src/main.cc.o$(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/src/main.cc.o
.PHONY : src/main.cc.osrc/main.i: src/main.cc.i.PHONY : src/main.i# target to preprocess a source file
src/main.cc.i:$(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/src/main.cc.i$(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/src/main.cc.i
.PHONY : src/main.cc.isrc/main.s: src/main.cc.s.PHONY : src/main.s# target to generate assembly for a file
src/main.cc.s:$(MAKE) -f CMakeFiles/main_test.dir/build.make CMakeFiles/main_test.dir/src/main.cc.s$(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/src/main.cc.s
.PHONY : src/main.cc.ssrc/reply.o: src/reply.cc.o.PHONY : src/reply.o# target to build an object file
src/reply.cc.o:$(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/src/reply.cc.o
.PHONY : src/reply.cc.osrc/reply.i: src/reply.cc.i.PHONY : src/reply.i# target to preprocess a source file
src/reply.cc.i:$(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/src/reply.cc.i
.PHONY : src/reply.cc.isrc/reply.s: src/reply.cc.s.PHONY : src/reply.s# target to generate assembly for a file
src/reply.cc.s:$(MAKE) -f CMakeFiles/reply.dir/build.make CMakeFiles/reply.dir/src/reply.cc.s
.PHONY : src/reply.cc.s# Help Target
help:@echo "The following are some of the valid targets for this Makefile:"@echo "... all (the default if no target is provided)"@echo "... clean"@echo "... depend"@echo "... rebuild_cache"@echo "... main_test"@echo "... main"@echo "... edit_cache"@echo "... reply"@echo "... src/main.o"@echo "... src/main.i"@echo "... src/main.s"@echo "... src/reply.o"@echo "... src/reply.i"@echo "... src/reply.s"
.PHONY : help#=============================================================================
# Special targets to cleanup operation of make.# Special rule to run CMake to check the build system integrity.
# No rule that depends on this can have commands that come from listfiles
# because they might be regenerated.
cmake_check_build_system:$(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
.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 令其生效即可!

​ 这样我们就成功设置了一个持久的变量!

​ 本文档完~

相关文章:

在Linux中开发C++

在Linux中开发C 本文档为本人在学习慕课网课程——[重学C &#xff0c;重构你的C知识体系]时的一些记录与思考&#xff0c;侵删。学习课程请支持正版&#xff01; 1. 搭建C/C编译环境 1.1 gcc 和 g 的区别 ​ 本质上没有太大区别&#xff0c;gcc 默认使用 c 编译器&#xf…...

【linux】Debian访问Debian上的共享目录

要在Debian系统上访问共享目录&#xff0c;通常意味着要访问通过网络共享的文件夹&#xff0c;比如通过SMB/CIFS&#xff08;Server Message Block/Common Internet File System&#xff09;协议共享的Windows共享文件夹。以下是访问共享目录的步骤&#xff1a; 1. 安装必要的…...

Postman Newman API 自动化测试快速入门

什么是 Newman&#xff1f; Newman 是一款专为 Postman 打造的命令行工具&#xff0c;旨在通过自动运行 Postman 集合和环境&#xff0c;实现 API 测试的自动化。它使得开发者无需打开 Postman 图形界面&#xff0c;即可直接在命令行中执行测试用例。 Newman 的优势 使用 Ne…...

Python之Web开发中级教程----ubuntu安装MySQL

Python之Web开发中级教程----ubuntu安装MySQL 进入/opt目录 cd /opt 更新软件源 sudo apt-get upgrade sudo apt-get update 3、安装Mysql server sudo apt-get install mysql-server 4、启动Mysql service mysql start 5、确认Mysql的状态 service mysql status 6、安全设…...

Flutter开发入门——路由

什么是路由&#xff1f; 移动端应用开发中&#xff0c;路由技术是一个非常重要的组成部分。路由技术负责管理应用中各个页面之间的跳转、导航以及参数传递等关键功能。在移动端应用中&#xff0c;一个高效、易于维护的路由系统对于提高开发效率和用户体验具有重要意义。 Flut…...

Acrobat Pro DC 2023:PDF编辑与管理的全新体验

Acrobat Pro DC 2023是一款功能强大且全面的PDF编辑和管理软件&#xff0c;旨在为用户提供卓越的PDF处理体验。以下是关于Acrobat Pro DC 2023软件功能特色的详细介绍&#xff1a; PDF编辑和管理&#xff1a;Acrobat Pro DC 2023拥有强大的PDF编辑功能&#xff0c;可以对PDF文…...

Linux课程_____网络管理

一、查看接口信息 1. ifconfig 查看所有活动网络接口的信息 ifconfig -a 查看所有网络接口信息 ifconfig 直接加网络接口 查看指定网络接口信息 1.1查看指定接口IP [rootlocalhost ~]# ip addr show ens160 1.2设置网络接口的IP地址 # ifconfig eth0 192.168.152.133 …...

ubuntu20.04_PX4_1.13

说在前面&#xff1a;&#xff08;最好找一个干净的Ubuntu系统&#xff09;如果配置环境的过程中出现很多编译的错误或者依赖冲突&#xff0c;还是建议新建一个虚拟机&#xff0c;或者重装Ubuntu系统&#xff0c;这样会避免很多麻烦&#x1f490; &#xff0c; 安装PX4 1.13.2 …...

12350安全生产举报热线系统解决方案

一、建设背景 1. 安全生产的重要性 在当今社会&#xff0c;安全生产是企业和社会发展中至关重要的一环。随着工业化的推进和技术的不断创新&#xff0c;各种生产活动中潜在的安全隐患也随之增加。为了及时发现和解决这些问题&#xff0c;各省市纷纷设立了安全生产举报热线。在…...

Java 多线程(超详细讲解)上篇

多线程可以使程序在同一时间内执行多个操作&#xff0c;采用Java中的多线程机制可以使计算机资源得到更充分的利用&#xff0c;多线程技术在网络编程中有广泛的应用。一、进程与线程 进程是程序的一次动态执行过程&#xff0c;它是从代码加载、执行中到执行完毕的一个完整过程…...

15届蓝桥杯备赛(2)

文章目录 刷题笔记(2)二分查找在排序数组中查找元素的第一个和最后一个位置寻找旋转排序数组中的最小值搜索旋转排序数组 链表反转链表反转链表II 二叉树相同的树对称二叉树平衡二叉树二叉树的右视图验证二叉搜索树二叉树的最近公共祖先二叉搜索树的最近公共祖先二叉树层序遍历…...

使用Vuex构建网络打靶成绩管理系统及其测试页面平台思路

使用Vuex构建网络打靶成绩管理系统及其测试页面平台 一、引言 在现代Web开发中&#xff0c;前端框架和状态管理库已经成为构建复杂应用的关键工具。Vue.js作为一个轻量级且易于上手的前端框架&#xff0c;结合Vuex这个专门为Vue.js设计的状态管理库&#xff0c;可以让我们更加…...

CPU的核心数与线程数对性能的影响是什么

我们经常在CPU的配置参数中看到核心数和线程数&#xff0c;那你知道CPU的核心数与线程数对性能的影响是什么呢&#xff1f;核心数和线程数是越多越好吗&#xff1f;要弄清楚这个问题&#xff0c;我们必须先了解以下几个基础知识。 什么是CPU核心&#xff1f; CPU核心&#xf…...

Web前端-HTML

HTML 负责页面的结构&#xff08;页面的元素和内容&#xff09; HTML由标签组成&#xff0c;标签都是预定义好的。例如<a>展示超链接&#xff0c;使用<img>展示图片&#xff0c;<vedio>展示视频。 HTML代码直接在浏览器中运行&#xff0c;HTML标签由浏览器…...

【LLMs+小羊驼】23.03.Vicuna: 类似GPT4的开源聊天机器人( 90%* ChatGPT Quality)

官方在线demo: https://chat.lmsys.org/ Github项目代码&#xff1a;https://github.com/lm-sys/FastChat 官方博客&#xff1a;Vicuna: An Open-Source Chatbot Impressing GPT-4 with 90% ChatGPT Quality 模型下载: https://huggingface.co/lmsys/vicuna-7b-v1.5 | 所有的模…...

详细了解CSS

1.1 样式定义方式 行内样式表&#xff08;inline style sheet&#xff09; 直接定义在标签的style属性中。 作用范围&#xff1a;仅对当前标签产生影响。 例如&#xff1a; <img src"/images/mountain.jpg" alt"" style"width: 300px; height:…...

Java基础-IO流

文章目录 1.文件1.基本介绍2.常用的文件操作1.创建文件的相关构造器和方法代码实例结果 2.获取文件相关信息代码实例结果 3.目录的删除和文件删除代码实例 2.IO流原理及分类IO流原理IO流分类 3.FileInputStream1.类图2.代码实例3.结果 4.FileOutputStream1.类图2.案例代码实例 …...

MySQL的基本概念

一.MySQL概念&#xff1a; 你可以把MySQL想象成一个大杂货店&#xff0c;里面有很多货架&#xff0c;每个货架上摆放着不同种类的商品&#xff0c;MySQLMySQ就像是这个杂货店的后台库存管理系统。 1.表格&#xff08;货架&#xff09;&#xff1a;每个货架上摆放商品&#xff0…...

如何入职车载测试

以下课件都可以学习&#xff0c;一对一教你如何入职车载 可以学习的内容如下&#xff1a;第一&#xff1a;仪表项目、导航项目、车控项目、OTA升级项目、UDS诊断项目。第二&#xff1a;DBC数据库制作、CDD数据库制作。第三&#xff1a;项目规范文档阅读、调查表理解。第四&…...

【物联网】Modbus 协议简介

Modbus 协议简介 QingHub设计器在设计物联网数据采集时不可避免的需要针对Modbus协议的设备做相关数据采集&#xff0c;这里就我们的实际项目经验分享Modbus协议 你可以通过QingHub作业直接体验试用&#xff0c;也可以根据手册开发相应的代码块。 qinghub项目已经全面开源。 …...

网络编程-套接字相关基础知识

1.1. Socket简介 套接字&#xff08;socket&#xff09;是一种通信机制&#xff0c;凭借这种机制&#xff0c; 客户端<->服务器 模型的通信方式既可以在本地设备上进行&#xff0c;也可以跨网络进行。 Socket英文原意是“孔”或者“插座”的意思&#xff0c;在网络编程…...

基于Python的医疗机构药品及耗材进销存信息管理系统

技术&#xff1a;pythonmysqlvue 一、系统背景 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本医疗机构药品及耗材信息管理系统就是在这样的大环境下诞生&#x…...

Java学习笔记(14)

常用API Java已经写好的各种功能的java类 Math Final修饰&#xff0c;不能被继承 因为是静态static的&#xff0c;所以使用方法不用创建对象&#xff0c;使用里面的方法直接 math.方法名 就行 常用方法 Abs,ceil,floor,round,max,minm,pow,sqrt,cbrt,random Abs要注意参数的…...

联合和枚举

联合体 联合体和结构体类似&#xff0c;也有多个成员构成&#xff0c;但编译器只为最大的成员分配足够的空间。 联合体最大的特点是所有的成员共用同一块内存空间。也叫共用体。 union Un { int i; struct s { char c1; char c2; char c…...

《深入Linux内核架构》第2章 进程管理和调度 (3)

目录 2.5 调度器的实现 2.5.1 概观 2.5.2 数据结构 2.5.3 处理优先级 2.5.3.1 nice和prior 2.5.3.2 vruntime 2.5.3.3 weight权重 2.5.4 核心调度器 2.5 调度器的实现 调度器的任务&#xff1a; 1. 执行调度策略。 2. 执行上下文切换。 无论用户态抢占&#xff0c;还是…...

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Refresh)

可以进行页面下拉操作并显示刷新动效的容器组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 支持单个子组件。 从API version 11开始&#xff0c;Refresh子组件会跟随手势下拉而下移…...

数据资产管理解决方案:构建高效、安全的数据生态体系

在数字化时代&#xff0c;数据已成为企业最重要的资产之一。然而&#xff0c;如何有效管理和利用这些数据资产&#xff0c;却是许多企业面临的难题。本文将详细介绍数据资产管理解决方案&#xff0c;帮助企业构建高效、安全的数据生态体系。 一、引言 在信息化浪潮的推动下&a…...

Visual Studio 2013 - 调试模式下查看监视窗口

Visual Studio 2013 - 调试模式下查看监视窗口 1. 监视窗口References 1. 监视窗口 Ctrl Alt W&#xff0c;1-4&#xff1a;监视窗口 (数字键不能使用小键盘) or 调试 -> 窗口 -> 监视 -> 监视 1-4 调试状态下使用&#xff1a; 在窗口中点击空白行&#xff0c;…...

CTF 题型 SSRF攻击例题总结

CTF 题型 SSRF攻击&例题总结 文章目录 CTF 题型 SSRF攻击&例题总结Server-side Request Forgery 服务端请求伪造SSRF的利用面1 任意文件读取 前提是知道要读取的文件名2 探测内网资源3 使用gopher协议扩展攻击面Gopher协议 &#xff08;注意是70端口&#xff09;python…...

【Swing】Java Swing实现省市区选择编辑器

【Swing】Java Swing实现省市区选择编辑器 1.需求描述2.需求实现3.效果展示 系统&#xff1a;Win10 JDK&#xff1a;1.8.0_351 IDEA&#xff1a;2022.3.3 1.需求描述 在公司的一个 Swing 的项目上需要实现一个选择省市区的编辑器&#xff0c;这还是第一次做这种编辑器&#xf…...