编译

1. 整体流程

  1. 预处理
  • #include是一个来自C语言的宏命令,在编译器进行编译之前把它后面所写的那个文件的内容,完完整整地、一字不改地包含到当前的文件中来,没有其它任何作用与副功能的。系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后再到 C++ 安装目录找。
  • 把同一个头文件多次用include包含进cpp文件里,可能会出现不良后果。如果在头文件开头使用#ifndef...#endif,那么在第一次include后,就已经有了相应的宏定义,第二次再试图include时,就不再满足#ifndef的条件,也就不会再次执行include。#pragma once也可以起到类似作用。 </br>

显然,在预处理的过程中,被include的文件的内容已经被复制到了对应的地方,因此编译过程中不会再处理被include的文件。

  1. 编译 这个过程就是把源文件转换成二进制的机器码,即生成.o.obj 目标文件。
  • 目标文件中没有定义的函数和变量就视作一个个符号,并把这些符号放进符号表(symbol table)中,然后在链接过程中再去找具体的定义。
  • 编译器在这个过程中针对源代码执行优化。
  1. 链接 在链接的时候,需要在makefile里面说明需要连接哪个 .o.obj 文件,此时,连接器会去这个 .o.obj 文件中找在 b.cpp 中实现的函数,再把他们 buildmakefile 中指定的那个可以执行文件中。通常,C++ 编译器会在每个 .o 或 .obj 文件中都去找一下所需要的符号,而不是只在某个文件中找或者说找到一个就不找了。因此,如果在几个不同文件中实现了同一个函数,或者定义了同一个全局变量,链接的时候就会提示 “redefined”或者multiple definition of ..

2. 注意事项

  1. 一个符号,在整个程序中可以被声明多次,但能且仅能被定义一次。
  2. 根据上述编译流程,如果被include的文件中有函数或变量的定义,那么当它被多个文件包含时,这些函数和变量就被定义了多次。在链接过程中就会出现类似“多次定义”的错误。但有三个例外
    • const 对象的定义。因为全局的 const 对象默认是没有 extern 的声明的,所以它只在当前文件中有效,在链接时也不会为此类对象查找定义。
    • 类(class)的定义。
    • 头文件中可以写内联函数(inline)的定义。因为内联函数在预处理过程中就会被直接复制到函数被调用的地方,就像include的复制一样,也不会在符号表中添加符号。在编译和链接过程中,也就不会出现“找到一个符号的多个定义”的错误。

GCC编译器

Ubuntu中安装和切换多版本GCC编译器

  1. 安装GCC
# 只安装 GCC 软件包
sudo apt install gcc 
# 或者安装 build-essential 软件包,该软件包包含 GCC 及一系列开发工具,如 make、g++ 和dpkg-dev。
sudo apt install build-essential 

clang

install

  1. download source code
  2. 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)
  1. 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

  1. pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。
  2. 你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。
  3. 不必担心宏名冲突了,也就不会出现宏名冲突引发的问题。大型项目的编译速度也因此提高了一些。
  4. 缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。
  5. 这种方式不支持跨平台,不受一些较老版本的编译器支持.

#ifndef

  1. ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
  2. 当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。
  3. 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。
  4. 受C/C++语言标准的支持,不受编译器的任何限制.