编译
1. 整体流程
- 预处理
#include
是一个来自C
语言的宏命令,在编译器进行编译之前把它后面所写的那个文件的内容,完完整整地、一字不改地包含到当前的文件中来,没有其它任何作用与副功能的。系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后再到 C++ 安装目录找。- 把同一个头文件多次用
include
包含进cpp
文件里,可能会出现不良后果。如果在头文件开头使用#ifndef...#endif
,那么在第一次include后,就已经有了相应的宏定义,第二次再试图include时,就不再满足#ifndef
的条件,也就不会再次执行include。#pragma once
也可以起到类似作用。 </br>
显然,在预处理的过程中,被include的文件的内容已经被复制到了对应的地方,因此编译过程中不会再处理被include的文件。
- 编译
这个过程就是把源文件转换成二进制的机器码,即生成
.o
或.obj
目标文件。
- 目标文件中没有定义的函数和变量就视作一个个符号,并把这些符号放进符号表(symbol table)中,然后在链接过程中再去找具体的定义。
- 编译器在这个过程中针对源代码执行优化。
- 链接
在链接的时候,需要在
makefile
里面说明需要连接哪个.o
或.obj
文件,此时,连接器会去这个.o
或.obj
文件中找在 b.cpp 中实现的函数,再把他们build
到makefile
中指定的那个可以执行文件中。通常,C++ 编译器会在每个 .o 或 .obj 文件中都去找一下所需要的符号,而不是只在某个文件中找或者说找到一个就不找了。因此,如果在几个不同文件中实现了同一个函数,或者定义了同一个全局变量,链接的时候就会提示 “redefined”或者multiple definition of ..
。
2. 注意事项
- 一个符号,在整个程序中可以被声明多次,但能且仅能被定义一次。
- 根据上述编译流程,如果被include的文件中有函数或变量的定义,那么当它被多个文件包含时,这些函数和变量就被定义了多次。在链接过程中就会出现类似“多次定义”的错误。但有三个例外
- const 对象的定义。因为全局的 const 对象默认是没有 extern 的声明的,所以它只在当前文件中有效,在链接时也不会为此类对象查找定义。
- 类(class)的定义。
- 头文件中可以写内联函数(inline)的定义。因为内联函数在预处理过程中就会被直接复制到函数被调用的地方,就像include的复制一样,也不会在符号表中添加符号。在编译和链接过程中,也就不会出现“找到一个符号的多个定义”的错误。
GCC编译器
Ubuntu中安装和切换多版本GCC编译器
- 安装GCC
# 只安装 GCC 软件包
sudo apt install gcc
# 或者安装 build-essential 软件包,该软件包包含 GCC 及一系列开发工具,如 make、g++ 和dpkg-dev。
sudo apt install build-essential
clang
install
- download source code
- compile
mkdir build && cd build
cmake ../llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;openmp;clang-tools-extra"
cmake --build ../build --target clangd
cmd | meaning |
---|---|
DLLVM_ENABLE_PROJECTS |
This option tells LLVM’s CMake build system which projects (that are part of the LLVM monorepo) should be built. |
clang |
This enables the Clang compiler frontend to be built. |
openmp |
Add openmp support. If it’s omitted, the generated compiler can’t compile code that uses openmp |
clang-tools-extra |
This enables additional Clang-based tools such as clangd (language server for IDEs), clang-format (automatic code formatter), clang-tidy (static code analysis tool), include-what-you-use (helps reduce unnecessary #include directives) |
- build and install
cmake --build . --target install
You can also install through command sudo apt install clang
. For ubuntu 20, clang 10 will be installed by default. You can also specify version by command sudo apt install clang-11
to install Clang 11
or sudo apt install clang-12
to install Clang 12.
You can check version of clang using command clang --version
or clang++ --version
.
change compiler
add compiler to options, taka clang as example. change path to your clang installation path.
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
select compiler
# For each command, you will see a prompt listing all available alternatives. Type the number corresponding to clang and press Enter.
sudo update-alternatives --config cc
sudo update-alternatives --config c++
check
cc --version
c++ --version
ninja
make
other
ifndef, pragma once
#pragma once
pragma once
一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。- 你无法对一个头文件中的一段代码作
pragma once
声明,而只能针对文件。 - 不必担心宏名冲突了,也就不会出现宏名冲突引发的问题。大型项目的编译速度也因此提高了一些。
- 缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。
- 这种方式不支持跨平台,不受一些较老版本的编译器支持.
#ifndef
- ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
- 当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。
- 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。
- 受C/C++语言标准的支持,不受编译器的任何限制.