0%

C++编译原理

本地开发时,可以通过指定–no-release-bcloud参数跳过release.bcloud脚本的执行。

尽量避免依赖一些大型模块,例如baidu/third-party/cuda,该代码库体积高达7G。bcloud分布式集群上已经部署了cuda,无需再通过依赖模块的方式来引入,直接使用宿主机上的环境即可。

编译过程

预处理、编译和汇编操作都是针对每一个编译单元分别进行的,即可以理解为编译器总是遍历每一个.cc文件,并对其分别执行预处理(头文件展开、宏定义替换、处理宏开关等)、编译(语法检查、生成汇编文件)和汇编(从汇编文件生成机器码文件)操作,得到ojbect文件。在gcc/g++命令后接多个.cc文件,即会分别对每个.cc文件加以处理。

预处理阶段会包头文件找不到、变量/函数未定义错误,编译阶段会报语法错误。

编译过程只依赖于第三方依赖的头文件搜索路径,链接过程只依赖于第三方库的搜索路径,源文件间从来不相互依赖。

gcc/g++编译参数

常用参数:

param demo Usage
-x -x c or -x c++ 显式指定编译语言
-o -o main 显式指定编译输出名称
-E -E main -o main.i 只对编译对象进行预处理
-S -S main -o main.s 只对编译对象进行汇编
-c -c main.cc or -c main.s 从源文件或汇编文件生成对象文件
-v 显式打印编译过程

宏定义:

1
2
-Dmacro // #define macro
-Umacro // #undef macro

指定头文件搜索路径:

1
-Idir

在.cc文件中include头文件时候, gcc/g++ 会先在当前目录查找你所制定的头文件, 如果没有找到, 他回到默认的头文件目录找, 如果使用 -I 制定了目录,他会先在你所制定的目录查找, 然后再按常规的顺序去找。如果-I指定的目录中包含了多个不同版本的同名头文件,以靠前的-I指定的为准。

调试信息:

1
2
3
-C # 在预处理的时候, 不删除注释信息
-g # 指示编译器,在编译的时候,产生调试信息
-ggdb # 此选项将尽可能的生成 gdb 的可以使用的调试信息

参数传递:

1
2
-Wa,option # 此选项传递 option 给汇编程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会汇编程序。
-Wl.option # 此选项传递 option 给连接程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会连接程序。

链接库:

1
2
3
4
-lcurse # 指定编译时使用libcurse.so/libcurse.a库
-Ldir # 指定编译时候搜索库的路径
-static # 此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行
-share # 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库

优化选项:

1
2
3
-O0 、-O1 、-O2 、-O3 # 编译器的优化选项的 4 个级别,-O0 表示没有优化, -O1 为默认值,-O3 优化级别最高。
-w # 不生成任何警告信息
-wall # 生成所有警告信息

链接命令

通过如下命令将多个object文件生成静态库:

1
2
gcc -c file1.cc file2.cc file3.cc
ar rcs libxxx.a file1.o file2.o file3.o

通过如下一组命令生成动态库,如果动态库在编译阶段连接了某个静态库,该静态库需在编译需要指定-fPIC参数,否则会出现recompile with -fPIC的错误提示:

1
2
gcc -fPIC -c file1.cc file2.cc
gcc -shared file1.o file2.o -o libxxx.so

常见编译工具如何设置gcc/g++参数

对于基于configure构建的项目,可以在命令行通过cppFLAGS指定:

1
./configure --prefix=/usr/local/bin CPPFLAGS="-I/usr/local/foo/include"

对于基于cmake构建的项目,可以通过在CMakefile.txt中增加如下内容:

1
2
3
4
5
# method 1
target_compile_options(foo PRIVATE -Idir)
target_link_libraries(foo -lcurse)
# method 2
set (CMAKE_CXX_FLAGS "-Idir -lcurse")

对于基于BCLOUD构建的,可以通过在BCLOUD中添加如下内容指定:

1
2
CXXFLAGS('-std=c++17 -O3 -g -fPIC -msse4 -DNDEBUG')
CXXFLAGS('-Werror -Wextra -Wall')

补充其他常见BCLOUD使用命令:

1
2
3
4
5
6
7
8
9
CPPFLAGS() # 预处理参数
CFLAGS() # C文件预处理参数
CXXFLAGS() # C++文件预处理参数
LIBS() # 指定要链接的库
LDFLAGS("-lpthread -lcrypto -lrt") # 指定链接参数
HEADERS("util/*.h", "$INC") # 发布头文件
StaticLibrary("util", Sources(GLOB("./util/common.cpp"))) # 生成静态库
Application("hello-git", Sources(GLOB("*.cpp"))) # 生成可执行文件
UTApplication("common_test", Sources("util/common_test.cpp")) # 生成单测

gcc/g++命令示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gcc main.o -L../shared_lib -lcurse -o demo # just link
gcc -Wall -fPIC main.cpp -Wl,-L,../shared_lib -Wl,-lcurse -o demo # build & link
g++ -c \
-Wno-class-memaccess \
-std=c++17 \
-O3 \
-g \
-fPIC \
-msse4 \
-DNDEBUG \
-Werror \
-Wextra \
-Wall \
-Wlogical-op \
-Wdouble-promotion \
-fsized-deallocation \
-faligned-new \
-I/usr/include \
-o main


对象文件理解

1
cxxfilt

反汇编

查看ELF文件头:

1
2
readelf -d libfoo.so
objdum -f libfoo.so

语法错误

内存问题

错误现象:

示例如下,不正确的指针使用会导致预期外的内存修改,导致不可知的问题。

1
2
3
4
5
6
7
8
#include <iostream>

int main() {
char* p = nullptr;
*p = 'h'; // right
*(p + 1) = 'e'; // wrong
std::cout << *p << std::endl;
}

函数调用

不失一般性,这类错误通常是由于调用时错误地混淆了函数和变量导致的。

错误现象:

1
foo_bar.cc:100:10: error no match for call to (const Vector3d {aka const Eigen::Matrix<double, 3, 1>}) ()

错误分析:

这个报错是说对于类型const Vector3d不存在operator()可以调用,通常是我们错误得将变量当作函数调用所致。

错误现象:

1
foo_bar.cc:100:10: error: invalid use of member function template<int Size> typename Eigen::DenseBase<Derived>::CostFxiedSegmentReturntype<Size>::Type Eigen::DenseBase<Derived>::head() const [with int Size = Size; Derived = Eigen::Matrix<double, 3, 1>] (did you forget the () ?)

错误代码:

1
Eigen::Vector3d()::Zero().head<2>.norm();

错误分析:

错误的将函数当做了变量调用。

Proto错误

错误现象:

程序解析proto读到的信息和proto实际存储内容不一致。

错误分析:

解析proto文件时所使用的protobuf定义和构建proto文件时的protobuf定义不兼容。

链接错误

运行时库加载失败

错误信息:

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

错误分析:

该错误由运行时无法找到依赖对动态库导致,可以通过在LD_LIBRARY_PATH中添加相应动态库的路径解决。

错误信息:

1
2
3
/usr/bin/ld:libxxx.so: file format not recognized; treating as linker script
/usr/bin/ld:libxxx.so: syntax error
collect2: error: ld return 1 exist status

错误分析

该错误通常是由于依赖的libxxx.so文件存在,但是因为下载不完全等原因导致的文件损坏,使得该文件无法被解析,重新下载完整的库文件可以解决该问题。

------ 本文结束 ------