惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
GRAHAM CLULEY
Cloudbric
Cloudbric
L
LINUX DO - 最新话题
W
WeLiveSecurity
人人都是产品经理
人人都是产品经理
S
Security Affairs
Google Online Security Blog
Google Online Security Blog
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
罗磊的独立博客
博客园 - Franky
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术
博客园 - 聂微东
Hacker News - Newest:
Hacker News - Newest: "LLM"
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
The Cloudflare Blog
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
Troy Hunt's Blog
O
OpenAI News
博客园 - 司徒正美
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
I
Intezer
T
Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
H
Hacker News: Front Page
T
Tailwind CSS Blog
V
V2EX
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Security Archives - TechRepublic
Security Archives - TechRepublic
腾讯CDC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News

Kang

C/C++ 模板元编程学习 Ubuntu包管理 如何使用 CMake vscode插件配置 MSVC,GCC,Clang——不同C/C++编译器对比 开源贡献的一些规范 Github Pages + Hexo 搭建个人博客 将光猫设置为桥接 Ubuntu安装记录 Ubuntu包管理 Shell
C/C++编译知识
Yu Kang · 2024-12-09 · via Kang

使用 GCC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
kang@pro:~$ gcc --help
Usage: gcc [options] file...
Options:
-pass-exit-codes Exit with highest error code from a phase.
--help Display this information.
--target-help Display target specific command line options (including assembler and linker options).
--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...].
Display specific types of command line options.
(Use '-v --help' to display command line options of sub-processes).
--version Display compiler version information.
-dumpspecs Display all of the built in spec strings.
-dumpversion Display the version of the compiler.
-dumpmachine Display the compiler's target processor.
-foffload=<targets> Specify offloading targets.
-print-search-dirs Display the directories in the compiler's search path.
-print-libgcc-file-name Display the name of the compiler's companion library.
-print-file-name=<lib> Display the full path to library <lib>.
-print-prog-name=<prog> Display the full path to compiler component <prog>.
-print-multiarch Display the target's normalized GNU triplet, used as
a component in the library path.
-print-multi-directory Display the root directory for versions of libgcc.
-print-multi-lib Display the mapping between command line options and
multiple library search directories.
-print-multi-os-directory Display the relative path to OS libraries.
-print-sysroot Display the target libraries directory.
-print-sysroot-headers-suffix Display the sysroot suffix used to find headers.
-Wa,<options> Pass comma-separated <options> on to the assembler.
-Wp,<options> Pass comma-separated <options> on to the preprocessor.
-Wl,<options> Pass comma-separated <options> on to the linker.
-Xassembler <arg> Pass <arg> on to the assembler.
-Xpreprocessor <arg> Pass <arg> on to the preprocessor.
-Xlinker <arg> Pass <arg> on to the linker.
-save-temps Do not delete intermediate files.
-save-temps=<arg> Do not delete intermediate files.
-no-canonical-prefixes Do not canonicalize paths when building relative
prefixes to other gcc components.
-pipe Use pipes rather than intermediate files.
-time Time the execution of each subprocess.
-specs=<file> Override built-in specs with the contents of <file>.
-std=<standard> Assume that the input sources are for <standard>.
--sysroot=<directory> Use <directory> as the root directory for headers
and libraries.
-B <directory> Add <directory> to the compiler's search paths.
-v Display the programs invoked by the compiler.
-### Like -v but options quoted and commands not executed.
-E Preprocess only; do not compile, assemble or link.
-S Compile only; do not assemble or link.
-c Compile and assemble, but do not link.
-o <file> Place the output into <file>.
-pie Create a dynamically linked position independent
executable.
-shared Create a shared library.
-x <language> Specify the language of the following input files.
Permissible languages include: c c++ assembler none
'none' means revert to the default behavior of
guessing the language based on the file's extension.

Options starting with -g, -f, -m, -O, -W, or --param are automatically
passed on to the various sub-processes invoked by gcc. In order to pass
other options on to these processes the -W<letter> options must be used.

For bug reporting instructions, please see:
<file:///usr/share/doc/gcc-13/README.Bugs>.

一些参数可以单独使用,此时可以将单字母粘连,比如 -c -g 等价于 -cg
一些参数后要跟特定的值,可以用 =、空格、粘连赋值,比如 gcc -Iinclude1 -I=include2 -I include3(但也有可能一些参数只能遵循特定的形式)。

常用的参数项

1
2
3
4
5
6
7
8
9
10
11
12
-I/xxx      # 指定头文件搜索目录
-L/xxx # 指定库文件搜索目录
-l/xxx # 链接库文件

-c # 只编译和汇编,不链接,生成目标文件(.o)
-S # 只编译,不汇编,生成汇编代码文件(.S)

-g # 生成调试信息
-Wall # 打开几乎所有警告信息
-O<level> # 设置优化等级
-std=c11 # 指定使用的C语言标准
-fPIC # 生成位置无关代码

多文件

多文件编译,其实就是将每个文件编译后的 .o 文件链接后的产物,所以在 make 中会像下面一样,将每个部分单独写出来,这样修改时未被更改的部分无需重复编译(可以利用通配符在 Makefile 中进一步进行抽象)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
main: main.o hello.o
gcc main.o hello.o -o main
main.o: main.c
gcc -c main.c -o main.o
hello.o: hello.c
gcc -c hello.c -o hello.o

# 进一步抽象描述
CC = gcc
SRC = main.c hello.c
OBJ = $(SRC:.c=.o)
main: $(OBJ)
$(CC) $^ -o $@
%.o: %.c
$(CC) -c $^ -o $@

#include 如果是 "",则会先在当前目录查找,失败后才会在系统路径查找;
如果是 <>,则直接在系统路径中查找。
在编译时指定的 -I,与系统目录等价,所以也可以被 <> 发现,借此避免使用冗长的相对路径格式。
虽然编译时正确找到头文件,但是代码补全插件找不到,写代码不舒服。不过既然编译没问题,可以在编译时将头文件目录这种要求搞成一个配置文件,也就是 compile_commands.json 文件。直接通过 Makefile 构建的,可以通过 Bear 生成该文件;通过 CMake 配置的,可以加一个配置选项 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON,在 build 目录即可生成该文件。

链接库

下载使用第三方库

一般引用第三方库是通过包管理器(apt 等)下载相应的包,由于 apt 会下载到系统路径,所以编写代码时可以直接 #include 相应第三方库的头文件,而在编译时同样需要链接第三方库。
因为头文件的作用是让我们复制一边声明,真正的库实现需要编译时手动链接。
比如在 C++ 中使用 fmt 库,通过 -lfmt 链接该库。

因为链接时还需要添加上具体的库名,不手动去看并不知道有哪些,此时可以通过 pkg-config 获取一个包的库名字等。

1
2
3
4
5
6
7
# 获得库的链接参数
kang@pro:~$ pkg-config libgvc --libs
-lgvc -lcgraph -lcdt

# 获得头文件目录的链接参数
kang@pro:~$ pkg-config libgvc --cflags
-I/usr/include/graphviz

以上返回是由包中的 pkgconfig 目录中的内容决定的。

链接多个 .o 文件和库文件时,放置顺序会影响结果。目标文件的未定义符号,只会在它后面的目标文件或库中寻找,不会再看前面的。所以,被依赖的要放在后面

包的组成

一个第三方库大致有以下部分:

1
2
3
4
5
6
# 有些本身是工具库存在的可能没有 bin/; 同样的,一些主要提供可执行程序直接使用的可能没有 include/
.
|- bin # 可执行文件
|- include # 头文件
|- lib # 静态库和动态库
|_ share # 文档等

而直接安装一个第三方库,比如 Ubuntu 下的 deb 包,安装后以上文件会自动放入对应的系统路径 /usr/xxx 中,而在编译器编译时,这些路径会进行默认搜索,所以头文件可以直接找到,库文件也直接写名字即可链接。
而如果放在一个一般路径,在编译时则需要指定相应目录。

1
2
# -I 指定头文件目录; -L 指定库文件目录; -l 指定库名字
g++ main.c -I/xxx/include -L/xxx/lib -lfmt -o main

对于 gcc,可以通过以下命令查看默认搜索的头文件目录和库目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 打印出gcc自身安装目录和库搜索目录等
kang@pro:~$ gcc -print-search-dirs
install: /usr/lib/gcc/x86_64-linux-gnu/13/
programs: =/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/
libraries: =/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib/:/lib/x86_64-linux-gnu/13/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/13/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../:/lib/:/usr/lib/

# -E 预处理; -v 详细输出; - 从标准输入读取;
# 用一个空内容,查看预处理阶段会进行哪些操作,可以发现默认的头搜索目录
kang@pro:~$ gcc -E -v - < /dev/null
Using built-in specs.
COLLECT_GCC=gcc
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 13.2.0-23ubuntu4' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-13 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-13-uJ7kn6/gcc-13-13.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-13-uJ7kn6/gcc-13-13.2.0/debian/tmp-gcn/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.2.0 (Ubuntu 13.2.0-23ubuntu4)
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
/usr/libexec/gcc/x86_64-linux-gnu/13/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu - -mtune=generic -march=x86-64 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -dumpbase -
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/13/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
# 0 "<stdin>"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "<stdin>"
COMPILER_PATH=/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'

对于开源的第三方库(以及要编写自己的开源项目的话),可以通过更改配置来自定义编译的结果。在编译很多项目时,往往会经历 configure->make->make install 三部曲(很像使用 CMake 的流程,相比之下 CMake 新多了)。
在用户视角下,首先我们要运行开发者提供的 configure 脚本,如下图所示,这个过程会生成 Makefile 文件,用户可以选择加参数自定义配置,比如 ./configure -prefix=/xxx

随后,利用 Makefile 文件进行构建,并将软件安装到对应的位置。
开发者可以使用 GNU 构建系统(GNU Build System)来构建支持以上流程的软件体系,包含三件套:autoconf, antomakelibtool.

运行时库

为了提升C语言的开发效率,C语言标准定义了一系列常用的函数,这些函数统称为C库函数。C标准仅规定了这些函数的原型,而具体的实现则留给了各个支持C语言标准的编译器。因此,每个编译器通常会提供超出标准C的实现,形成了所谓的C运行时库(C Run Time Library)。
C运行时库中的启动函数负责程序的初始化工作,包括设置堆栈、初始化全局和静态变量、调用构造函数等;程序结束时,运行时库也负责调用析构函数、释放资源等清理工作。所以,C程序都需要链接C运行时库。
与C语言类似,C++也有自己的标准,并提供了相应的支持库,即C++运行时库或C++标准库。由于C++设计时考虑了与C语言的兼容性,C++标准库不仅包含了C标准库,还扩展了包括标准模板库(STL)在内的更多功能。

通过 ldd 可以列出可执行文件或共享库所依赖的共享库。

可以看出,默认情况下一个简单的C程序会动态链接 libc.so.6,我们也可以指定静态链接,此时无需依赖运行时库,但文件体积也相应增大。

参考:

  1. 【技术杂谈】C编译体系
  2. GNU构建系统和Autotool
  3. 终于理解了什么是c/c++运行时库,以及libcmt msvcrt等内容