ProgrammingModel

Programming Model

img
  • 预处理器 将.c/.cpp源代码进行 预处理 (preprocess)得到.i文件;预处理包括宏替换导入头文件去除注释等行为

  • 编译器 对.i源文件进行 编译操作 (compile)得到含汇编指令的.s汇编代码(assemble code);该步骤会进行语法检查,但不会发现逻辑错误;汇编代码可用vim打开

image-20210917222609207

  • 汇编器 对.s文件进一步 汇编操作 (assemble)得到.o 二进制目标(object)文件

  • 链接器 对.o文件进行 链接 (link)得到可执行文件

对应的的命令行(e.g. gcc):

gcc -E <>.c -o <>.i
gcc -S <>.i -o <>.s
gcc -c <>.s -o <>.o
gcc <>.o -o <> # 无参数
一步到位
gcc -o <output_file_name> <input_file_name>

备注

符号未定义、符号重定义 `` duplicate symbol `` 是链接时期的错误

其他常用编译选项:

-I:指定头文件目录 (e.g. -I.. -I ..可加空格也可不加空格,可相对路径)
-O:是否进行优化(0<1<2<3,其中0为不优化;1为default优化等级;3为最高级别的优化)
-D:指定宏(e.g. -D DEBUG,等效于程序里面的 #define DEBUG)
-L: 指定库目录搜素路径
-Wall:是否输出警告信息
-g:在生成的代码中添加调试信息,配合gdb调试工具使用
-std:设置c++标准(e.g. -std=c++14)
-fPIC: Position-Independent Code
-rpath: 将动态库路径写到可执行文件中(hard code),会hide LD_LIBRARY_PATH的效果

Compile Feature

c++的编译特点:不同于一些高级语言,它们的编译单元是整个模块,c++的编译单元是以文件为单位。每个.c/.cc/.cxx/.cpp源文件是一个独立的编译单元,所以优化时只能基于当前文件进行优化,而不能从模块(多个文件)的角度进行优化。

image-20220403135300524
  • 在add_executable的文件不是合在一起进行编译的,而是依然基于文件单元进行编译的

image-20220416163848215

Dynamic Library

CLI

查看一个target文件运行时需要链接的动态库
ldd <file>
查看一个正在运行的进程所链接的动态库
pldd <pid>
image-20210916224735535
  • 动态库的配置文档为 etc/ld.so.conf ,具体又引用了/etc/ld.so.conf.d 下的文件

详细查看已有的动态链接信息
该配置文档是有关加载到内存的动态链接库而非所有动态链接库
ldconfig: configure dynamic linker run-time bindings
sudo ldconfig -v

File

File Type

stripped:说明该文件的符号表信息已被去除

img

not stripped:保留符号表信息和调试信息

img

提示

要符号表和调试信息(可以知道某个函数在哪个源文件的第几行)可以加入编译选项 -g (左边为无g,右边加上g)

img img

Output Printable Character

可打印的字符应该是ASCII码
strings <file_name>

Symbol

  • ELF文件有两种符号表:.symtab and .dynsym,动态链接库为ELF

  • .dynsym 保留了 .symtab 中的全局符号(global symbols)。命令strip可以去掉动态库文件中的 symtab ,但不会去掉 .dynsym

  • 使用nm时提示no symbol是因为默认找的 symtab 表被strip了,因此只能查看动态符号表 .dynsym,加上-D

  • .symtab 用于动态库自身链接过程,一旦链接完成了,就不再需要了; .dynsym表包含动态链接器(dynamic linker)运行期时寻找的symbol.

  • nm 默认.symtab section.

CLI

可查看object/target的
nm [option] <file>
-A: 输出文件名
-c: demangle
-D:查看动态符号表
-l:显示对应的行号
-u: 显示未定义的符号
objdump [option] <file>
查看符号的可见性
readelf -s B

image-20220416175011251

备注

每个.o文件都有一个符号表,符号有两种分类,一个是全局符号,一个是本地符号,本模块的非静态函数和全局变量都是其他模块可见和可用的;静态函数和静态变量都是只有本模块可见的,其他模块不可使用

Demangle

echo <...> | c++filt

img

Q&A

extern "C"

告知g++编译器这部分代码是c库的代码(不需对该部分内容进行symbol mangling),使得C库的符号能够被顺利找到而链接成功

Translation Unit

According to standard C++wayback machine link): A translation unit is the basic unit of compilation in C++. It consists of the contents of a single source file, plus the contents of any header files directly or indirectly included by it, minus those lines that were ignored using conditional preprocessing statements.

precompile source file的#号是什么?

一种特殊的注释

image-20211002140045353

C++ "multiple definition of .. first defined here"

Include guards only help against including the same header in one Translation unit (cpp file) multiple times, not against including and compiling the same function in multiple TUs.

头文件宏能够保证一个翻译单元没有重复的符号(symbol);而不能保证多个翻译单元合起来后(A+B)没有重复的符号

Why not duplicate symbol?

File

  • c++

// A.cpp
int num_A1 = 0;
int num_A2 = 2;

// B.cpp
int num_A1 = 5;
extern int num_A2;
int main() {
  // num_A2 = num_A2 + 1;
}
  • CMakeLists.txt

cmake_minimum_required(VERSION 3.11)
project(project)

set(CMAKE_BUILD_TYPE DEBUG)

add_library(B B.cpp)
add_executable(A A.cpp)
target_link_libraries(A B)

Experiment

  • 通过实验证明,最终生成的可执行文件并不包含A.cpp/num_A1这个变量。即通过对比使用和不使用 target_link_libraries(A B) 的可执行文件A的符号信息(nm命令行)来判别是否一致。

img
  • 另外:A.cpp一旦显式触发用上 B.cpp 那边的符号之后就会成功触发报错

img

备注

另外如果使用的是动态库的话,反而可以顺利通过编译

备注

测试平台windows/ubuntu20.04 g++9.4.0

Conclusion

暂无权威信息佐证,以下均为基于实验的猜测:

(1)静态库把所有symbol都加到target

(2)动态库是把只需要的symbol加到target

(3)对静态库链接时,如果target不需要静态库的任何symbol链接器ld就干脆不导入静态库的任何symbol;但凡有参考的话,就会触发添加所有的symbol

Reference