c/c++编译基础知识
GNU
GNU(GNU’s Not Unix!)是一个由理查德·斯托曼(Richard
Stallman)在1983年发起的自由软件项目,旨在创建一个完全自由的操作系统,包括操作系统的内核、编译器、工具、库、文本编辑器、邮件系统、新闻系统等。GNU项目是自由软件运动的核心,其目标是确保用户有使用、复制、修改和分发软件的自由。GNU项目的一些关键组件包括:
GCC(GNU Compiler Collection):一套支持多种编程语言的编译器,包括C、C++、Objective-C、Fortran、Ada、Go等。
GDB(GNU Debugger):一个强大的程序调试工具,用于调试C和C++程序。
Glibc(GNU C Library):GNU项目开发的C标准库实现,广泛用于Linux操作系统。
Gawk:一个文本处理工具,类似于Unix的awk。
Grep:一个用于搜索文本的工具,类似于Unix的grep。
Gnuplot:一个绘图工具,用于绘制函数、数据等图形。
Emacs:一个功能强大的文本编辑器。
Bash(Bourne Again Shell):一种Unix shell和命令语言。
GnuPG(GNU Privacy Guard):一个加密软件,用于数据加密和数字签名。
Autoconf和Automake:用于生成配置脚本和Makefile的工具。
GNU项目还开发了Hurd,这是一个旨在替代Unix内核的操作系统内核,但由于种种原因,Hurd并没有得到广泛的应用。相反,Linux内核(由林纳斯·托瓦兹开发)与GNU工具集合成为今天广泛使用的Linux操作系统。
GNU项目强调软件的自由性,其发布的软件遵循GNU通用公共许可证(GPL),这是一种“传染性”的许可证,要求所有基于GPL软件的衍生作品也必须以GPL发布,从而确保软件的自由性得以传递。
GCC
GCC,全称为GNU编译器集合(GNU Compiler
Collection),是一个由GNU项目开发的编程语言编译器系统。它支持多种编程语言,包括但不限于C、C++、Objective-C、Fortran、Ada、Go和D等。GCC是自由软件,遵循GNU通用公共许可证(GPL)发布。GCC的主要特点包括:
跨平台:GCC可以在多种操作系统上编译源代码,包括Linux、Windows、macOS等。
优化:GCC提供了多种优化选项,以提高生成代码的执行效率。
调试支持:GCC能够生成调试信息,与GDB(GNU Debugger)等调试工具配合使用。
多种语言支持:GCC支持多种编程语言的编译,使其成为一个多功能的编译器集合。
自动并行化:GCC的一些版本提供了自动并行化的功能,可以自动将某些循环转换为并行执行的形式。
内联汇编:GCC支持内联汇编语言,允许开发者在高级语言代码中嵌入汇编指令。
插件支持:GCC允许通过插件扩展其功能。
交叉编译:GCC支持交叉编译,即在一种平台上编译出另一种平台上运行的代码。
GCC是许多Linux发行版默认的系统编译器,并且广泛用于嵌入式系统、高性能计算和各种软件开发项目中。由于其强大的功能和灵活性,GCC是开源社区中最受欢迎的编译器之一。
gcc 和 g++
GCC(GNU Compiler Collection)是一个编译器集合,它包括多个不同的编译器,其中最常用的是针对C语言的
gcc
和针对C++语言的g++
。以下是gcc
和g++
的简要说明:
gcc:
- 这是GNU项目提供的C语言编译器。
- 它用于编译C语言源代码文件(通常以
.c
为扩展名)。gcc
支持多种操作系统和硬件平台,可以生成针对不同目标的可执行文件或库文件。g++:
g++
是GNU编译器集合中的C++语言编译器。- 它用于编译C++语言源代码文件(通常以
.cpp
或.cc
为扩展名)。g++
同样支持跨平台编译,并提供了C++特有的特性支持,如类、模板、异常处理等。两者的主要区别在于它们支持的语言特性和标准库。
gcc
专门用于C语言,而g++
则用于C++语言。尽管它们在某些底层组件上共享代码,但它们的前端(解析源代码的部分)是不同的,以适应各自语言的语法和特性。使用示例:
编译C语言程序:
sh gcc -o program program.c
这将编译program.c
并生成名为program
的可执行文件。编译C++语言程序:
sh g++ -o program program.cpp
这将编译program.cpp
并生成名为program
的可执行文件。在实际使用中,您可能还会使用到其他选项和参数,例如优化标志(
-O2
)、调试信息生成(-g
)、链接特定库(-L
和-l
)等。此外,g++
在编译混合C和C++代码时,也可以调用gcc
来编译C语言部分的代码。
gcc 和 g++常见编译选项
gcc
和g++
提供了一系列的编译命令选项,用于控制编译过程的不同方面。以下是一些常用的编译命令选项:
输出可执行文件的名称:
sh -o <file>
指定输出的可执行文件名。优化选项:
-O0
:没有优化。-O1
:启用基本优化。-O2
:进一步优化。-O3
:更多的优化,可能包括更激进的优化策略。-Os
:优化生成更小的代码。调试信息:
sh -g
生成调试信息。包括搜索路径:
sh -I <dir>
添加头文件搜索路径。库搜索路径:
sh -L <dir> -l <library>
指定库文件的搜索路径,并链接指定的库。链接时的库:
sh -l <library>
链接时包括特定的库。静态链接:
sh -static
使用静态链接而不是动态链接。C++ 特定选项(在使用
g++
时):sh -std=c++11 -std=c++14 -std=c++17 -std=c++20
指定C++语言标准。警告选项:
-Wall
:打开几乎所有的警告信息。-Wextra
:打开额外的警告信息。-Werror
:将所有警告当作错误处理。编译特定文件:
-c
只编译和生成目标文件(
.o
文件),不进行链接。生成依赖文件:
-MF <depfile>
生成包含依赖关系的文件。
使用多个处理器编译:
-j <n>
使用
n
个处理器进行编译,可以加快编译速度。指定架构:
-m32 -m64
指定生成32位或64位代码。
禁用标准库:
-nostdlib
编译时不使用标准库。
内联汇编:
-S
编译后只生成汇编代码。
这些只是
gcc
和g++
提供的众多选项中的一部分。根据项目的具体需求,您可能还会使用到其他一些选项。在命令行中使用gcc --help
或g++ --help
可以查看完整的选项列表和说明。
c++ 程序运行的过程分析
- 编写完的 c/c++程序想要运行起来,需要四个步骤:预处理、编译、汇编、链接。
- 编写 c++程序:main.cpp
#include <iostream>
using namespace std;
int add (int a, int b)
{
return a+b;
}
int main()
{
int x = 30;
int y = 100;
int ret = add(x, y);
cout << "sum: " << ret << endl;
return 0;
}
预处理
在C++编译过程中,预处理是一个重要的步骤,它发生在实际编译之前。以下是预处理的一些主要作用:
宏替换: 预处理器会处理宏定义(由
#define
指令定义)和宏展开。例如,如果你在代码中使用了#define PI 3.14159
,预处理器会将代码中所有的PI
替换为3.14159
。文件包含: 使用
#include
指令包含标准库头文件或用户自定义的头文件。预处理器会将这些文件的内容插入到该指令的位置。条件编译: 预处理器根据条件编译指令(如
#ifdef
、#ifndef
、#if
、#else
、#elif
和#endif
)来决定是否包含特定的代码段。这允许开发者根据不同的编译条件编写不同的代码。行控制:
#line
指令可以用来控制编译器报告错误和警告时显示的文件名和行号。错误诊断: 使用
#error
指令可以在编译时生成错误信息。编译时间计算:
#if
指令可以用来进行编译时常量的计算和比较。版本控制和编译选项: 预处理器可以根据不同的定义来启用或禁用特定的代码段,这通常用于处理不同操作系统或编译器的特性。
依赖性分析: 预处理器可以生成依赖性信息,这对于自动构建系统来说是有用的,以确保在头文件更改时重新编译依赖的文件。
函数宏和宏函数: 预处理器处理函数宏和宏函数的调用和展开。
预定义宏:
编译器会定义一些预定义宏,如__FILE__
、__LINE__
、__DATE__
、__TIME__
等,它们在预处理时被替换为当前文件名、行号、日期和时间。预处理的结果是一个或多个预处理过的源文件,这些文件随后将被编译器进行语法分析、语义分析和代码生成等后续编译步骤。预处理是C++源代码转换为可执行程序的第一步,对于编译器正确理解源代码结构至关重要。
- 预处理命令:
g++ -E main.cpp -o main.i
- 生成文件:
main.i
,仍是文本文件,可以用任意文本编辑器打开。
编译
编译是将高级语言代码转换成低级代码(通常是汇编代码或中间代码)的过程,侧重于语言特性的处理和代码优化。 编译阶段的作用:
- 语法分析:检查代码是否符合C++语言的语法规则。
- 词法分析:将源代码分解成一系列的词素(tokens)。
- 语义分析:确保代码符合C++的语义规则,比如类型检查和作用域解析。
- 抽象语法树(AST)构建:将源代码转换成AST,这是一种树状结构,表示程序的逻辑。
- 优化:对AST进行优化,以提高程序性能或减少资源消耗。
- 代码生成:将优化后的AST转换成中间代码或汇编代码。
在一些编译器实现中,编译阶段可能直接生成机器代码,而在其他情况下,编译器可能先生成汇编代码,然后由汇编器转换成机器代码。无论哪种情况,编译和汇编都是将源代码转换成可执行程序的必要步骤。
- 编译命令:
g++ -S main.cpp -o main.s
- 生成文件:
main.s
汇编
汇编是将汇编语言代码转换成机器代码的过程,侧重于目标机器的指令集和内存管理。 汇编阶段的作用:
- 转换为汇编语言:将编译器生成的中间代码转换成目标机器的汇编语言。
- 符号替换:将代码中的符号(如变量名和常量)替换为内存地址或立即数。
- 地址分配:为代码和数据分配具体的内存地址。
- 生成汇编文件:创建包含汇编指令的文件,这些指令可以被汇编器进一步处理。
- 错误检测:检测汇编代码中的错误,如非法指令或不正确的地址引用。
- 准备链接:生成的目标文件通常是可重定位的,为后续的链接阶段做准备。
- 汇编命令:
g++ -c main.cpp -o main.o
或者直接用汇编文件命令as main.s -o main.o
- 生成文件:
main.o
链接
C++编译的链接阶段是将编译器生成的目标文件(通常是
.o
或.obj
文件)组合成最终可执行文件或库文件的过程。以下是链接阶段的主要作用:
符号解析: 链接器解析目标文件中的符号引用,确保每个外部符号(变量和函数)都能正确地关联到其定义。
地址和空间分配: 链接器为程序中的代码和数据分配最终的内存地址,并决定它们在可执行文件中的布局。
代码和数据合并: 链接器将所有目标文件中的代码和数据合并成一个单一的可执行文件或库文件。
库链接: 如果程序依赖于外部库,链接器会将这些库中的代码和数据与目标文件合并。这包括静态库和动态库的链接。
重定位: 链接器处理重定位项,将代码和数据中的相对地址转换为绝对地址。
生成可执行文件: 对于最终的可执行文件,链接器生成一个可在操作系统上运行的程序。
生成库文件: 对于库文件,链接器生成一个包含多个函数和变量定义的文件,这些可以在其他程序中使用。
符号表生成: 链接器生成一个符号表,列出了程序中所有的符号及其地址。
调试信息整合: 如果编译时带有调试信息,链接器会整合这些信息到最终的可执行文件或库中。
资源文件处理:
链接器处理程序中使用的资源文件,如图形、声音等,并将它们整合到最终的程序中。优化:
某些链接器或与之集成的工具可能会进行进一步的优化,如死代码消除。生成调试符号:
如果需要,链接器可以生成调试符号文件,如DWARF格式的符号信息,以支持后续的调试工作。错误检测:
链接器在链接过程中会检测未解决的外部引用或其他链接错误,并报告这些问题。链接是编译过程的最后阶段,确保了所有编译后生成的片段能够正确地组合在一起,形成一个完整的程序。链接后的程序可以直接在目标机器上运行或被其他程序作为依赖库使用。
- 链接命令:
g++ -o main main.o
- 生成文件:
main
验证
- 文件目录:
.
├── main
├── main.cpp
├── main.i
├── main.o
└── main.s
- 验证可执行程序:
./main
- 输出结果:
sum:130
admin@bogon test % ls
main main.cpp main.i main.o main.s
admin@bogon test % ./main
sum: 130