C/C++
2024/10/3大约 8 分钟
C/C++
C语言编译流程
- C语言编译主要经过四个过程
- 预处理(Preprocessing)
- 处理以#开头的预处理指令,比如#include、#define、#ifdef等。
- 生成的文件后缀为.i
- 编译(Compiling)
- 将预处理后的文件转换成汇编语言
- 生成的文件后缀为.s
- 汇编(Assembling)
- 将汇编语言代码转换成目标二进制文件
- 生成的文件后缀为.o
- 链接(Linking)
- 将多个目标文件及所需的库文件组合生成最终的可执行文件
- 预处理(Preprocessing)
gcc/g++编译器使用
可用选项
- gcc/g++常用的一些功能选项:
- -E 只激活预处理,这个不生成文件,你需要
-o把它重定向到一个输出文件里面 - -S 编译到汇编语言不进行汇编和链接
- -c 编译到目标代码
- -o 结果输出到对应文件
- -static 此选项对生成的文件采用静态链接
- -g 生成调试信息。GNU 调试器可利用该信息,一般配合优化项
-o0使用 - -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库
- -w 不生成任何警告信息
- -Wall 生成所有警告信息
- -E 只激活预处理,这个不生成文件,你需要
编译选项,编译成.o文件时使用
-D: 添加额外宏信息, 例如: -DDEBUG、-DTHREAD=8,如果需要传入"需要转义,比如-DMESSAGE=""H"-m64: 指定编译为 64 位应用程序-std=: 指定编译标准,例如:-std=c++11、-std=c++14-g: 包含调试信息-w: 不显示警告-O: 优化等级,通常使用:-O3-I: 加在头文件路径前fPIC: (Position-Independent Code), 产生的没有绝对地址,全部使用相对地址,代码可以被加载到内存的任意位置,且可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的
链接选项,将内容链接成可执行文件时使用
-l: 加在库名前面-L: 加在库路径前面-Wl,<选项>: 将逗号分隔的 <选项> 传递给链接器-rpath=: "运行" 的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找- 优化选项见gcc编译优化(O0、O1、O2、O3、Os)
可执行文件编译
gcc和g++的用法相同,此处列出的是gcc的编译流程
预处理:
gcc -E [.c源文件] -o [自定义输出文件名.i]编译成汇编语言(隐藏了预处理操作):
gcc -S [.c源文件]编译.o的object文件(二进制文件,可用于链接):
gcc -c [.c源文件] [.c源文件] [...] (可选选项:-o [自定文件名])编译最终可执行文件
gcc [.c源文件] [.c源文件] [...] (可选选项:-o [自定文件名])
库文件编译
- 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行
- 库文件有两种,静态库和动态库,区别是:
- 静态库在程序的链接阶段被复制到了程序中,打开程序就会将库加载到内存
- 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用
- 静态库(.a文件)
- 优点
- 静态库被打包到应用程序中加载速度快。
- 发布程序无需提供静态库,移植方便。
- 缺点
- 消耗系统资源,浪费内存,对于相同的库,由于该库写入在程序之中,因此在加载多个程序的过程中加载了多次次这个库。
- 更新、部署、发布麻烦。
- 优点
- 动态库的路径与依赖关系
- 动态库的路径:当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径
- 动态库的依赖关系:程序启动之后,动态库会被动态加载到内存中,通过
ldd(list dynamic dependencies)命令检查动态库依赖关系
- 动态库(.so文件)
- 优点
- 可以实现进程间资源共享,库同时间只会加载一次
- 更新、部署、发布简单
- 可以控制何时加载动态库
- 缺点
- 加载速度比静态库慢
- 发布程序时需要提供依赖的动态库
- 优点
Windows环境下静态库名字是.lib,动态库名字是.dll- 对于
Windows而言,动态库还有对应的.lib文件需要在编译时引入,这个静态库用于帮助程序知道.dll中有哪些数据 .dll库的链接,需要直接放入编译出的可执行文件同目录下,或使用Windows提供的动态链接函数,如果希望暴露符号也需要__declspec(dllexport)导出- 如果使用
CLion编译最终库文件请点击构建对应任务的按钮(不是运行任务),并且要注意CLion中每个无关联的构建是分开的任务 - 如果使用
CLion编译最终可执行文件需要添加-static-libgcc -static-libstdc++编译参数,不然无法双击运行(命令行编译不需要)
- 对于
静态库
编库(先转成.o文件,再编成lib[自定库名].a)
gcc -c [.c源文件] [.c源文件] [...] (可选选项:-o [自定文件名]) ar -r lib[自定库名].a [.o文件] [.o文件] [...]链接
gcc [main文件] -o [自定义输出可执行文件名] -l[库名] -L[库的路径]
动态库
编库
第一种做法, 先转成.o文件,再编成.so文件
gcc -c -fpic [.c源文件] [.c源文件] [...] gcc -shared [.o文件] [.o文件] [...] -o lib[库名].so第二种做法,直接转成.so文件
gcc -fpic -shared [.c源文件] [.c源文件] [...] -o lib[库名].so
链接
gcc [main文件] -o [自定义输出可执行文件名] -l[库名] -L[库所在路径] -Wl,-rpath=[库所在路径]
编译中断
由于默认无论出现什么样的警告信息都不会中断编译,这对大量文件的编译是不友好的,这些警告信息中混杂着真正的编写错误,比如:
-Werror=return-type指的是没提供正确类型的返回值如果没有发现,可能会出现奇怪的错误,比如:未给函数声明添加;结尾,并运行了程序,会得到下列输出,这些错误
debug是很难发现具体位置的stdarg.h:40:27: error: storage class specified for parameter ‘__gnuc_va_list’ 40 | typedef __builtin_va_list __gnuc_va_list;对于应该视为错误的信息应该直接中断编译,只需要在编译时添加
-Werror=警告类型就可以在出现警告时终止编译,当然也有下列调控方法-w:不提示警告,-Wno-警告名称可以单独限制某些警告-Werror:将所有警告当做错误处理-W额外警告类型:提示不在all列表中的警告,他们可以通过-Wextra统一开启,有以下几种-Wshadow:当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告。很有用的选项,建议打开。 -Wall 并不会打开此项。-Wpointer-arith:对函数指针或者void *类型的指针进行算术操作时给出警告。也很有用。 -Wall 并不会打开此项。-Wcast-qual:当强制转化丢掉了类型修饰符时给出警告。 -Wall 并不会打开此项。-Waggregate-return:如果定义或调用了返回结构体或联合体的函数,编译器就发出警告。-Winline:无论是声明为 inline 或者是指定了-finline-functions 选项,如果某函数不能内联,编译器都将发出警告。如果你的代码含有很多 inline 函数的话,这是很有用的选项。-Werror:把警告当作错误。出现任何警告就放弃编译。-Wunreachable-code:如果编译器探测到永远不会执行到的代码,就给出警告。也是比较有用的选项。-Wcast-align:一旦某个指针类型强制转换导致目标所需的地址对齐增加时,编译器就发出警告。-Wundef:当一个没有定义的符号出现在 #if 中时,给出警告。-Wredundant-decls:如果在同一个可见域内某定义多次声明,编译器就发出警告,即使这些重复声明有效并且毫无差别。
-Wall:提示所有警告(默认)-Wpedantic:要求编译器严格遵守语言的ISO标准,会被视为警告的包括长度为0的数组、结构体或枚举中最后的多余括号等- 结合
-fmax-errors=N:出现N次错误时终止编译
# 当遇到大部分应该视为错误的警告时停止编译 gcc -Wall -Wextra -Wpedantic -Werror=return-type -Werror=uninitialized \ -Werror=null-dereference -Werror=format -Werror=format-security \ -Werror=sign-compare -Werror=sequence-point -Werror=sizeof-array-argument \ -o program program.c
程序
内存存储结构
- 堆:存放动态申请的数据,主要存放复杂对象、容器、动态数组,其中的内容需要需要手动释放
- 栈:存放静态分配的数据,存放基本数据类型、函数调用参数、局部变量,生命周期跟随函数调用,自动释放
面向对象
| 继承方式 | 基类 public 成员在子类中 | 基类 protected 成员在子类中 | 外部能否通过子类对象访问基类 public 成员 |
|---|---|---|---|
public | 仍然是 public | 仍然是 protected | ✔️ |
protected | 变成 protected | 仍然是 protected | ❌ |
private | 变成 private | 变成 private | ❌ |
