编译

1. 整体流程

  1. 预处理
    • #include是一个来自C语言的宏命令,在编译器进行编译之前把它后面所写的那个文件的内容,完完整整地、一字不改地包含到当前的文件中来,没有其它任何作用与副功能的。系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后再到 C++ 安装目录找。
    • 把同一个头文件多次用include包含进cpp文件里,可能会出现不良后果。如果在头文件开头使用#ifndef...#endif,那么在第一次include后,就已经有了相应的宏定义,第二次再试图include时,就不再满足#ifndef的条件,也就不会再次执行include。#pragma once也可以起到类似作用。
      显然,在预处理的过程中,被include的文件的内容已经被复制到了对应的地方,因此编译过程中不会再处理被include的文件。
  2. 编译 这个过程就是把源文件转换成二进制的机器码,即生成.o.obj 目标文件。
    • 目标文件中没有定义的函数和变量就视作一个个符号,并把这些符号放进符号表(symbol table)中,然后在链接过程中再去找具体的定义。
    • 编译器在这个过程中针对源代码执行优化。
  3. 链接 在链接的时候,需要在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 
    

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++语言标准的支持,不受编译器的任何限制.