从零开始 TensorRT(5)C++ 篇:g++、CMake、VS Code 环境入门

前言

学习资料:
B站视频:基于 VSCode 和 CMake 实现 C/C++ 开发
B站视频:Cherno C++ 教程

  从本文开始,正式进入 C++ 部分。由于个人 C++ 零基础,仅了解一些 Python,所以学习时的痛点更偏向于 C++ 的基础,例如 VS Code、CMake、C++ 语法等,TensorRT 的部分和在 Python 中使用大同小异。

  本文主要解决以下入门时的痛点:
(1)TensorRT 的示例或是 github 上的项目大多使用 Makefile 或 CMakeLists,并在命令行输入相关指令进行编译和运行,虽然通常修改一些路径依赖就能跑通,但仍是懵逼的。Makefile 看起来复杂一些,仅学习使用 CMake。
(2)在 IDE 如 VS Code 中运行和调试代码。
(3)建议学习 Cherno 教程中编译、链接的部分。

1. 安装 gcc、g++、gdb、cmake

sudo apt update
sudo apt install build-essential gdb
sudo apt install cmake

2. g++ 编译过程

将 C++ 源码编译成可执行文件,并且这个过程可以分为四个步骤

g++ main.cpp -o main

(1)预处理

g++ -E main.cpp -o main.i

(2)编译
将预处理后的文件翻译为汇编代码

g++ -S main.i -o main.s

(3)汇编
将汇编代码转换为机器可执行的目标文件(通常是二进制文件)

g++ -c main.s -o main.o

(4)链接
将目标文件与其他目标文件和库链接在一起,生成最终的可执行文件

g++ main.o -o main

3. g++ 编译参数

(1)调试
-g 使编译器在生成的可执行文件中包含调试信息

g++ -g main.cpp -o main

(2)优化
启用编译器优化

g++ -O2 main.cpp -o main

(3)库文件
-l 指定库文件,会在默认搜索路径中查找指定库文件,默认路径与操作系统有关
Linux 通常为 /lib/usr/lib/usr/local/lib
Windows 通常为 C:\Windows\System32、系统环境变量 LIB 指定路径、应用程序所在目录
通常库名称会省略前缀 lib 和文件名后缀。

g++ main.cpp -lfile -o main

-L 指定库文件路径,库文件不在默认搜索路径中时用于指定搜索路径

g++ main.cpp -L/path/to/libs -lfile -o main

(4)头文件
-I 指定头文件的搜索路径,编译器会先在指定路径中查找,再去系统默认路径中查找。Linux 默认头文件路径为 /usr/include/usr/local/include

g++ -I/path/to/include main.cpp -o main

(5)警告
-Wall 启用编译器提供的警告信息
-w 关闭所有警告信息

(6)设置编译标准

g++ -std=c++11 main.cpp -o main

(7)定义预处理器宏
-D<name>[=<value>] 用于定义预处理器宏,通常用于控制条件编译

g++ -DDEBUG main.cpp -o main

例如下面代码只有在 DEBUG 定义时才会运行

#ifdef DEBUGstd::cout << "Debug" << std::endl;
#endif

4. g++ 实战案例

源码信息

项目文件结构

.
├── include
│   └── Log.h
├── main.cpp
└── src└── Log.cpp

main.cpp

#include <iostream>
#include "Log.h"int main()
{Log("Hello World!");
}

Log.h

#pragma oncevoid Log(const char* message);

Log.cpp

#include <iostream>
#include "Log.h"void Log(const char* message)
{std::cout << message << std::endl;std::cin.get();
}

直接编译

未指定输出文件名时会默认输出 a.out

g++ main.cpp src/Log.cpp -Iinclude

生成静态库

cd src
g++ -c Log.cpp -I../include
ar rs libLog.a Log.o
cd ..
g++ main.cpp -Iinclude -Lsrc -lLog -o static_mian.out

生成动态库

cd src
g++ Log.cpp -I../include -fPIC -shared -o libLog.so
cd ..
g++ main.cpp -Iinclude -Lsrc -lLog -o dyna_mian.out

生成动态库的语句可以按如下方式分步骤执行

g++ Log.cpp -I../include -c -fPIC
g++ -shared -o libLog.so Log.o

静态库和动态库的区别

静态库动态库
后缀Linux .a
Windows .lib
Linux .so
Windows .dll
链接方式嵌入到可执行文件中编译时不会被嵌入到可执行文件中,仅在运行时加载和链接
文件大小可执行文件较大,每个使用该库的可执行文件都包含库的副本可执行文件较小,但会增加运行时的内存占用
部署运行可执行文件与静态库完全独立,不需要额外的运行时支持依赖于动态库,需要同时提供可执行文件和相应动态库的安装部署
更新维护静态库代码发生变化时,每个依赖它的可执行文件都需要重新编译以包含最新的库代码动态库代码发生变化时,只需重新编译动态库,不需要重新编译可执行文件,可执行文件在运行时会自动使用最新版本的动态库
加载和启动加载时间较长,启动速度较快加载时间较短,启动速度较慢
多个程序可共享同一个动态库,从而减少内存占用

动态库路径问题

  由于 libLog.sosrc 文件夹下,在学习的教程案例中,运行 ./dyna_mian.out 会报错找不到动态库文件。其原因是 src 并不在系统默认寻找动态库路径中,此时需要执行 LD_LIBRARY_PATH=src ./dyna_mian.out 将其添加到动态库路径中才能正常运行。
  然而,在测试时可以直接成功运行 dyna_mian.out,弹幕评论区也有不少遇到了相同的情况,于是寻找了一下原因,以下为探寻过程,所有方法都来自 ChatGPT3.5。

  先总结一下,其根本原因是编译器优化导致 Log 函数没有使用,所以程序运行根本不需要生成的动态库文件。之后找到方法使编译器强制使用 Log 函数,才得到了和教程中一样的结果,在此基础上再解决找不到路径的问题。

查找问题所在

(1)Linux 系统动态库搜索路径配置
/etc/ld.so.conf 文件包含了动态库搜索路径的配置,其内容如下:

include /etc/ld.so.conf.d/*.conf

通过 find /etc/ld.so.conf.d/ -type f -name "*.conf" 查找对应文件,结果如下:

/etc/ld.so.conf.d/cuda-11-8.conf
/etc/ld.so.conf.d/cuda-12-2.conf
/etc/ld.so.conf.d/libc.conf
/etc/ld.so.conf.d/fakeroot-x86_64-linux-gnu.conf
/etc/ld.so.conf.d/i386-linux-gnu.conf
/etc/ld.so.conf.d/x86_64-linux-gnu.conf

这些文件的内容就不一一列出了,总之未包含工程中的 src 路径。

(2)动态库缓存
ldconfig -p 可以查看系统当前的动态库缓存,即系统已知的动态库路径
ldconfig -p | grep libLog 筛选查询结果,得知 libLog 也不在缓存中。
sudo ldconfig 可更新动态库缓存

(3)可执行文件依赖关系
ldd ./dyna_mian.out 查看可执行文件所依赖的动态库及其路径信息

	linux-vdso.so.1 (0x00007fff3c1b5000)libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f218a02e000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2189e3c000)libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2189ced000)/lib64/ld-linux-x86-64.so.2 (0x00007f218a23a000)libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f2189cd2000)

(4)追踪运行时的动态库加载情况

strace -e open,access ./dyna_mian.outaccess("/etc/ld.so.preload", R_OK)      = -1 ENOENT (没有那个文件或目录)
Hello World!+++ exited with 0 +++

根据输出结果,运行时并没有尝试打开或访问动态库文件,libLog.so 在运行时可能已经加载并缓存。在 Linux 系统中,动态库加载一般是由动态连接器 ld.sold-linux.so 负责,它会根据系统的动态库搜索路径以及运行时路径(RPATH)来查询加载动态库。

下方指令可以更全面的查看动态库加载的情况,会输出详细的搜索路径和动态库加载过程,而其中并没有发现 libLog.so

LD_DEBUG=libs ./dyna_mian.out

(5)查看程序的符号表

nm -D ./dyna_mian.outU __cxa_atexitw __cxa_finalizew __gmon_start__w _ITM_deregisterTMCloneTablew _ITM_registerTMCloneTableU __libc_start_mainU _ZNSi3getEvU _ZNSolsEPFRSoS_EU _ZNSt8ios_base4InitC1EvU _ZNSt8ios_base4InitD1Ev
0000000000004160 B _ZSt3cin
0000000000004040 B _ZSt4coutU _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc

其中 U 表示未解析的符号,表明程序引用了一些 C++ 库的函数和对象,而输出中并没 Log 函数的符号,也就是编译器在优化过程中把函数剔除了,根本没有使用。

解决优化函数方法

第一个尝试的方法是用 -rdynamic 选项重新编译,会让编译器将所有符号都加入到可执行文件的动态符号表中,然而编译器仍然优化掉了 Log 函数。

g++ main.cpp -Iinclude -Lsrc -lLog -o dyna_mian.out -rdynamic

第二个尝试的方法可行,将 Log.hLog.cpp 分别按如下修改,强制编译器包含 Log 函数

#pragma once__attribute__((__visibility__("default"))) void Log(const char* message);
#include <iostream>
#include "Log.h"__attribute__((used)) void Log(const char* message)
{std::cout << message << std::endl;
}

查看程序的符号表:

nm -D ./dyna_mian.outU __cxa_atexitw __cxa_finalizew __gmon_start__w _ITM_deregisterTMCloneTablew _ITM_registerTMCloneTableU __libc_start_mainU _Z3LogPKcU _ZNSi3getEvU _ZNSt8ios_base4InitC1EvU _ZNSt8ios_base4InitD1Ev
0000000000004020 B _ZSt3cin

可以发现 U _Z3LogPKc 代表了 Log 函数,此时运行 ./dyna_mian.out 就会出现报错:

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

解决动态库路径方法

(1)运行时指定动态库搜索路径

LD_LIBRARY_PATH=src ./dyna_mian.out

(2)编译时设置运行时路径

g++ main.cpp -Iinclude -Lsrc -lLog -Wl,-rpath=./src -o dyna_mian.out

此时可以使用 ./dyna_mian.out 直接运行,通过 readelf -d dyna_mian.out 显示可执行文件中的动态段信息,通过 RPATHRUNPATH 字段可以查找到搜索路径。

 0x000000000000001d (RUNPATH)            Library runpath: [./src]

5. VS Code 与 CMake

插件安装

点击左侧扩展 → 搜索 C/C++ → 安装 C/C++ Extension Pack
其内部包含了 C/C++、C/C++ Themes、CMake、CMake Tools
在这里插入图片描述

示例一

在这里插入图片描述
  从最简单的示例开始,此时可以使用经常看到的指令,可以使用 ctrl+` 打开内部终端:

mkdir build
cd build
cmake ..
make
./helloworld

  但这是使用终端操作,删除之前的 build 文件夹后,在 VS Code 中可以点击上方运行 → 添加配置,此时左侧会多出一个 CMake 的按钮,如下图。我这里选择配置 GCC 9.4.0 x86_64-linux-gnu
在这里插入图片描述
  点击底部状态栏的生成按钮便会自动在 build 目录下生成可执行文件。旁边有调试和运行按钮,如果有多个可执行文件,可以在上图中 CMake 的调试和启动选项中修改目标文件。
在这里插入图片描述

CMake 常用指令

# 指定 CMake 最小版本要求
cmake_minimum_required(VERSION 3.10)
# 定义工程名称
project(HELLOWORLD)

添加生成可执行文件

# 类似 g++ main.cpp -o helloworld
add_executable(helloworld main.cpp)

添加头文件搜索路径

# 相当于 g++-I, ${PROJECT_SOURCE_DIR} 代表当前工程的文件
include_directories(${PROJECT_SOURCE_DIR})

添加库文件搜索路径

# 相当于 g++-L
link_directories(/usr/local/cuda/lib64)

添加需要链接的共享库

# 相当于 g++-l
target_link_libraries(helloworld Log)

生成库文件

# 生成 libLog.so 动态库
add_library(Log SHARED src/Log.cpp)
# 生成 libLog.a 静态库
add_library(Log STATIC src/Log.cpp)

定义变量

# 定义 SRC 变量, 其值为 file1.cpp file2.cpp
set(SRC file1.cpp file2.cpp)

添加编译参数

add_compile_options(-Wall -std=c++11 -O2)

常用变量

# 编译选项 gcc: CMAKE_C_FLAGS
# 编译选项 g++: CMAKE_CXX_FLAGS
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")# 编译类型: CMAKE_BUILD_TYPE
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_BUILD_TYPE Release)# 可执行文件输出存放路径: EXECUTABLE_OUTPUT_PATH
# 库文件输出存放路径: LIBRARY_OUTPUT_PATH

示例二

  这里沿用 g++ 实战案例的代码,如下图,现在需要编写 CMakeLists,其思路感觉和使用 g++ 编译非常相似。
在这里插入图片描述

直接编译

  参考之前直接编译的方式,只需要把头文件路径加进来,并且把两个 cpp 文件加入到可执行文件中即可。

cmake_minimum_required(VERSION 3.10)
project(HELLOWORLD)include_directories(${PROJECT_SOURCE_DIR}/include)add_executable(helloworld main.cpp src/Log.cpp)

生成库文件

  以生成动态库文件为例,库文件会默认生成在 build 文件夹下,这里我们用 lib 文件夹存放库文件,用 bin 文件夹存放可执行文件。
  这里将代码按照 解决优化函数方法 修改代码后进行测试,发现可以直接正常运行,使用 readelf -d ./build/helloworld 查看后发现,自带了 RUNPATH,这应该意味着内部生成的库文件路径会自动添加(也有可能和各种东西的版本有关),不需要手动使用 link_directories 添加库文件搜索路径。

cmake_minimum_required(VERSION 3.10)
project(HELLOWORLD)include_directories(${PROJECT_SOURCE_DIR}/include)set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)add_library(Log SHARED src/Log.cpp)add_executable(helloworld main.cpp)
target_link_libraries(helloworld Log)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2804807.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

nios ii开发随笔

错误一&#xff1a; d:/intelfpga/17.1/nios2eds/bin/gnu/h-x86_64-mingw32/bin/../lib/gcc/nios2-elf/5.3.0/../../../../../H-x86_64-mingw32/nios2-elf/bin/ld.exe: test.elf section .text will not fit in region ram_oc_xzs d:/intelfpga/17.1/nios2eds/bin/gnu/h-x86_6…

01_第一章 WEB开发概述(技术栈,交互模式,CS和BS模式,前后端分离)

文章目录 第一章 WEB概述1.1 JAVAWEB简介1.2 JAVAWEB技术栈1.3 JAVAWEB交互模式1.4 JAVAWEB的CS和BS模式1.5 JAVAWEB实现前后端分离 第一章 WEB概述 1.1 JAVAWEB简介 用Java技术来解决相关web互联网领域的技术栈.使用JAVAEE技术体系开发企业级互联网项目. 项目规模和架构模式与…

Linux:ACL权限,特殊位和隐藏属性

目录 一.什么是ACL 二.操作步骤 ① 添加测试目录、用户、组&#xff0c;并将用户添加到组 ② 修改目录的所有者和所属组 ③ 设定权限 ④ 为临时用户分配权限 ⑤ 验证acl权限 ⑥ 控制组的acl权限 三. 删除ACL权限 一.什么是ACL 访问控制列表 (Access Control List):ACL 通…

如何在 Tomcat 中为 Web 应用程序启用和配置缓存?

在Tomcat中为Web应用程序启用和配置缓存通常涉及到对Tomcat的连接器&#xff08;Connector&#xff09;进行配置&#xff0c;以及可能的话&#xff0c;配置Web应用程序本身以支持缓存。 1. 配置Tomcat连接器以启用缓存 Tomcat的连接器可以通过其配置来启用各种…

FFmpeg解析之avformat_find_stream_info函数

avformat_find_stream_info 的主要作用就是&#xff1a;解析媒体文件并获取相关的流信息 整体的逻辑如下图所示&#xff1a; /*** Read packets of a media file to get stream information. This* is useful for file formats with no headers such as MPEG. This* function…

LeetCode206: 反转链表.

题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 解题方法 假设链表为 1→2→3→∅&#xff0c;我们想要把它改成∅←1←2←3。在遍历链表时&#xff0c;将当前节点的 next指针改为指向前一个节点。由于节点没有引用其前一…

挑战杯 基于大数据的时间序列股价预测分析与可视化 - lstm

文章目录 1 前言2 时间序列的由来2.1 四种模型的名称&#xff1a; 3 数据预览4 理论公式4.1 协方差4.2 相关系数4.3 scikit-learn计算相关性 5 金融数据的时序分析5.1 数据概况5.2 序列变化情况计算 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &…

电表(2)stm32学习笔记-STLINK使用

stm32学习笔记-STLINK使用 使用ST-LINK调试程序进度表格 使用ST-LINK调试程序 说明 组成 总结 记录使用STLINK进行项目的烧写和调试&#xff0c;旨在高效的进行代码调试学习工具包括笔记本、keil5MDK、stm32f030c8t6电表主机、STLINK V2、导线、电表代码总的来说&#xff0…

yolov8-seg dnn调用

接上篇一直更换torch、opencv版本都无法解决这个问题&#xff08;seg调用dnn报错&#xff09;。那问题会不会出在yolov8源码本身呢。yolov8的讨论区基本都看过了&#xff0c;我决定尝试在其前身yolov5的讨论区上找找我不信没人遇到这个问题。很快找到下面的讨论第一个帖子&…

Project_Euler-03 题解

Project_Euler-03 题解 题目 思路 首先排除掉暴力求解&#xff0c;虽然也可以得出答案&#xff0c;但是我在我仅仅只有二颗核心的服务器上跑了很久很久… 尝试另一种方法&#xff1a; 首先要知道一个知识&#xff0c;所有的数都可以拆解成为素数因子平方连乘的形式&#xff…

Spring Boot与HikariCP:性能卓越的数据库连接池

点击下载《Spring Boot与HikariCP&#xff1a;性能卓越的数据库连接池》 1. 前言 本文将详细介绍Spring Boot中如何使用HikariCP作为数据库连接池&#xff0c;包括其工作原理、优势分析、配置步骤以及代码示例。通过本文&#xff0c;读者将能够轻松集成HikariCP到Spring Boot…

PCIe P2P DMA全景解读

温馨提醒&#xff1a;本文主要分为5个部分&#xff0c;总计4842字&#xff0c;需要时间较长&#xff0c;建议先收藏&#xff01; P2P DMA简介 P2P DMA软硬件支持 CXL P2P DMA原理差异 P2P DMA应用场景 P2P DMA技术挑战 一、P2P DMA简介 P2P DMA&#xff08;Peer-to-Peer…

解决ubuntu系统cannot find -lc++abi: No such file or directory

随着CentOS的没落&#xff0c;使用ubuntu的越来越多&#xff0c;而且国外貌似也比较流行使用ubuntu&#xff0c;像LLVM/Clang就有专门针对ubuntu编译二进制发布文件&#xff1a; ubuntu本身也可以直接通过apt install命令来安装编译好的clang编译器。不过目前22.04版本下最高…

SpringMVC 学习(二)之第一个 SpringMVC 案例

目录 1 通过 Maven 创建一个 JavaWeb 工程 2 配置 web.xml 文件 3 创建 SpringMVC 配置文件 spring-mvc.xml 4 创建控制器 HelloController 5 创建视图 index.jsp 和 success.jsp 6 运行过程 7 参考文档 1 通过 Maven 创建一个 JavaWeb 工程 可以参考以下博文&#x…

java——File类和字符集

目录 File类File类的常用操作&#xff1a;案例&#xff1a;文件搜索的实现案例&#xff1a;递归文件夹删除 字符集几种常见的字符集总结字符集的编码和解码 File类 File是java.io.包下的类&#xff0c;File类的对象&#xff0c;用于代表当前操作系统的文件&#xff08;可以是文…

成功解决TypeError: can‘t multiply sequence by non-int of type ‘float‘

&#x1f525; 成功解决TypeError: can’t multiply sequence by non-int of type ‘float’ &#x1f4c5; 日期&#xff1a;2024年2月23日 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化…

大数据-数据可视化-环境部署vue+echarts+显示案例

文章目录 一、安装node.js1 打开火狐浏览器,下载Node.js2 进行解压3 配置环境变量4 配置生效二、安装vue脚手架1 下载vue脚手架,耐心等待。三、创建vue项目并启动1 创建2 启动四、下载echarts.js与axios.js到本地。五、图表显示demo【以下所有操作均在centos上进行】 一、安…

【高德地图】Android高德地图控件交互详细介绍

&#x1f4d6;第5章 与地图控件交互 ✅控件交互&#x1f9ca;缩放按钮&#x1f9ca;指南针&#x1f9ca;定位按钮&#x1f9ca;地图Logo ✅手势交互&#x1f9ca;缩放手势&#x1f9ca;滑动手势&#x1f9ca;旋转手势&#x1f9ca;倾斜手势&#x1f9ca;指定屏幕中心点的手势操…

啊丢的刷题记录手册

1.洛谷题P1923 求第k小的数 题目描述 输入 n&#xff08;1≤n<5000000 且 n 为奇数&#xff09;个数字ai​&#xff08;1≤ai​<109&#xff09;&#xff0c;输出这些数字的第 k 小的数。最小的数是第 0 小。 请尽量不要使用 nth_element 来写本题&#xff0c;因为本题…

Spring及工厂模式概述

文章目录 Spring 身世什么是 Spring什么是设计模式工厂设计模式什么是工厂设计模式简单的工厂设计模式通用的工厂设计 总结 在 Spring 框架出现之前&#xff0c;Java 开发者使用的主要是传统的 Java EE&#xff08;Java Enterprise Edition&#xff09;平台。Java EE 是一套用于…