传统编译型软件的总体示意图,展示了源文件被编译成目标文件
1 c/c++
C 语言简介
面世:1969
诞生定位:UNIX 操作系统的语言
最新版本:C99
特点:不提供垃圾回收、多线程支持等功能,也没有任何内置的复杂数据类型,是一门紧凑而高效的语言。
应用:目前新开发的 c 代码最常用于 CPU 性能要求高,或直接操作计算机硬件的场合,比如嵌入式设备。
C++ 语言简介
面世:1980年代早期
诞生定位:为基本的 C 语言增加面相对象的概念, C 的超集。
特点:包含了类、继承、模版的概念,以及标准 C 语言中未提供的复杂数据结构。
应用:相比 C,C++ 更适合编程开发,当谈它仍然支持嵌入式系统和高性能计算的开发。
1.1 编译工具
通常,能够编译 C 语言的编译器同时也支持 C++,比如:
- GCC(GNU 工具集,GNV Compiler Collection)
- 微软的 Visual Studio C++ 编译器
- Green Hills 编译器
- Intel C++ 编译器
GCC
发布: 发布于 1987 年
地位: 编译开源软件的事实标准,并广泛用于商业软件开发。
优势: 可以生成多种 CPU 类型的目标代码。
GCC 的工具链
按必要顺序调用相关工具,这个过程构成一个链条,称为工具链
方法。组成部分有
1)C 预处理器(C Preprocessor),用于扩展宏定义。
2)C 编译器(C Compiler),用于把源代码转译成汇编语言
3)汇编器(assembler),用于把汇编语言转译成目标文件
4)链接器(linker),用于把不同的目标文件联结成单个可执行程序
1.2 源文件
说明: C 语言有两种类型的文件
- C 源文件(.c):包含对函数和全局变量的定义
- C 头文件(.h):包含类型、宏、常量定义和函数声明
1.2.1 示例代码
1 | . |
main.c
1 |
|
hello.h
1 | // extern 暗示这个函数可能在别的源文件里定义, 这样一来就是,在程序中取代include “*.h”来声明函数 |
hello.c
1 |
|
makefile
1 | SRCS = main.c hello.c |
1.2.2 -c 选项
功能: 将源文件编译为目标文件。
1 | # 编译前 |
1.2.3 -E 选项
功能: 输入源文件,只处理 include
指令和宏扩展,不进行实际的编译。将处理后的文本在终端输出。
应用: 对预处理器的输出内容进行检查,可以查看貌似由宏、类型或常量定义产生的问题。
例如,gcc -E main.c
,输出如下:
1 |
|
1.3 汇编语言文件
-S 选项
功能: 输入源文件,输出汇编语言文件(.s)。
应用:如果怀疑编译器编译代码出错,而且你了解汇编语言,则可以通过这个选项判断是否编译器有问题。
例如,gcc -S hello.c
,生成 hello.s
,该文件内容如下
1 | .section __TEXT,__text,regular,pure_instructions |
1.4 目标文件
说明: 目标文件是机器码指令的容器,它不具备在计算机上执行的条件,因为还需要与其他目标文件以及所有必需的库文件进行链接。
1.4.1 -c 选项
功能: 输入源文件,生成目标文件。
例如,gcc -c hello.c
,生成 hello.o
1.4.2 分析目标文件 - file 指令
功能: 输入文件,输出文件的高层次信息。
例如,file hello.o
,输出如下
1 | hello.o: Mach-O 64-bit object x86_64 |
1.4.3 分析目标文件 - nm 命令
功能: 输入目标文件,输出函数或变量的定义情况。
应用: 通过对多有目标文件运行 nm 命令,就可以定位缺失的负号是在哪里引用的,在哪里定义的。
例如,nm hello.o
,输出如下
1 | 0000000000000000 T _hello # hello.o 中包含 hello 函数的定义 |
1.4.4 分析目标文件 - objdump 命令
功能: objdump 命令有许多不同选项,可以提供目标文件更详细的信息,是 file 和 nm 命令的超集; 也提供了把机器码反汇编成汇编语言的能力。
应用: 如果怀疑汇编器生成的机器码指令有错,就可以用它进行检查。
1.4.5 分析目标文件 - hexdump 命令
功能:检查文件的每一个原始字节,这种方法是比较原始的。
应用:如果由于某种原因(例如文件损坏了)导致 objjump 命令实效,那么 hexdump 命令可能就是最后的希望。
1.5 可执行程序
1.5.1 -o 选项
功能: 该选项可以只完成链接工作,也可以一次执行整个工具链。
仅完成链接
说明:用 -o 选项来提供程序名,并列出要链接的所有目标文件。
1 | $ gcc -o hello hello.o main.o |
执行整个工具链
说明:用 -o 选项来提供程序名,并列出要链接的所有源文件。
限制:小程序可以,大型构造系统不会这么做。
1 | $ gcc -o hello hello.c main.c |
1.6 静态程序库
说明: 静态程序库知识镀铬目标文件的归档文件,可以通过 ar 命令创建静态程序库。
1.6.1 ar 命令
功能: 处理静态库的工具,比如
-rs
:负责将目标文件归档为静态链接库文件-t
:还可以检查归档文件的内容
还有一些选项可以把目标文件从静态程序库中摘取出来,或把目标文件内容写回到磁盘文件(但对构造系统来说不是常用操作)。
1.6.2 创建静态库
c_demos/c_programming_a_modern_approach/15/fmt at master · laputa-er/c_demos · GitHub
这是我在另一本书看到的一个例子,写了一个 demo,提供了更健壮的对流读取和写入。我准备将其中 4 个文件做成一个静态库,依次是,
1 | . |
下面是制作静态库的整个过程,
1 | # 生成目标文件 |
1.6.3 使用静态库
链接静态库
示例如下,其中fmt.c 使用了上一步创建的静态库,用来完成对文本的格式化(具体功能可参考《C 语言程序设计-现代方法 15章》)
1 | ├── fmt.c |
编译主程序,然后将目标文件和静态库进行链接。注意,GCC 只会把静态库中真正用到的目标文件链接到可执行程序中。
1 | $ gcc -c fmt.c # 主程序 |
测试可执行程序
原始文本放在 test.txt 文件中,
1 | to test whether the program fmt work well. |
使用程序 fmt 整理 test.txt 的格式,并将整理好的文本输出,如下
1 | $ ./fmt < test.txt |
1.7 动态链接库
案例: 仍然使用上一节提到的例子。
1.7.1 创建动态程序库
相关 GCC 选项 | 说明 |
---|---|
-fPIC | 创建位置无关的目标文件(这种目标文件才能链接成动态链接库) |
-shared | 链接过程中使用该选项,告诉编译器链接出动态程序库。 |
1 | # 编译出目标文件 |
1.7.2 使用动态程序库
相关 GCC 选项 | 说明 |
---|---|
-L |
告诉链接器动态链接库所在目录 |
-l |
告诉链接器动态链接库的名字 |
1 | $ gcc -c fmt.c # 主程序 |
注意: ·l
选项后面使用的并不是动态链接库的文件本身的名字,需要有所变化,比如 libmystream.so
,使用时要写成 mystream
!
1.7.3 查看程序用到的动态程序库
ldd 命令
功能: 查看主程序执行时,需要把那个动态程序库载入内存。
扩展: mac 上没有这个命令,对应的命令是 otool
linux 环境
1 | $ export LD_LIBRARY_PATH=. # 指出自定义的动态链接库所在目录,注意在实际编码时使用相对路径会有安全漏洞 |
mac 环境
1 | $ otool -L fmt |
1.8 c++ 编译
g++ vs. gcc
- gcc 也可以编译 c++ ,但更推荐使用 g++ 来编译 c++。
- gcc 和 g++ 相比,编译阶段几乎是相同的,链接阶段g++默认链接c++库,gcc不会。
- 也可以用gcc编译cpp文件,但后面需要加一个选项-lstdc++,作用是链接c++库。
c++ 类型检查
c++ 可以在链接时执行类型检查,而 c 程序则必须在编译时进行所有的类型检查。