简析

编译过程

一般来说,C++程序会分为 头文件和源代码文件。
C++鼓励我们将组件函数放在单独的文件中,这就意味着会存在多个源代码文件,C++鼓励我们对这些源代码文件单独编译,然后将这些文件的编译版本链接。
单独编译后在链接的意义在于:如果我们要对一个源文件修改,我们就可以只对这个源文件编译,然后和其他文件的编译版本链接。

  • 比如下面这个程序,有一个有文件coordin.h,两个源代码文件 file1.cpp和file2.cpp
    file

coordin.h

#ifndef a
#define a
struct polar
{
double distance;
double angle;
};
struct rect
{
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif

file1.cpp

#include<iostream>
#include"coordin.h"
int main(){
using namespace std;
rect rplace;
polar pplace;

cout<<"Enter the x and y values:";
while (cin>>rplace.x>>rplace.y)
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout<<"Next two numbers (q to quit):";
}

}

file2.cpp

#include<iostream>
#include<cmath>
#include "coordin.h"

polar rect_to_polar(rect xypos ){
using namespace std;
polar answer;
answer.distance=sqrt(xypos.x*xypos.x+xypos.y*xypos.y);
answer.angle=atan2(xypos.y,xypos.x);
return answer;
}

void show_polar(polar dapos){
using namespace std;
const double rad_to_deg =57.29577951;

cout<<"distance=:"<<dapos.distance<<endl<<"angle=:"<<dapos.angle<<endl;
}

可以看出来,coordin.h里面就是函数原型和结构体声明,file1.cpp 和 file2.cpp里就是函数的定义。
这个程序就是把直角坐标化成极坐标。
使用命令行生成可执行文件,是非常简单的

g++ file1.cpp file2.cpp -o myfile.exe

g++帮你完成了很多工作,包括单独编译然后将目标代码文件,库代码和启动代码合并,并生成可执行文件.

注意:file1.cpp中没有include"file2.cpp",可见这两个源代码文件确实是单独编译的。

预处理

g++ -E .\file1.cpp -o file1.i

预处理后得到file1.i 文件。
预处理做的事情有:

  1. 展开所有的宏定义并删除 #define
  2. 处理所有的条件编译指令,例如 #if #else #endif #ifndef …
  3. 把所有的 #include 替换为头文件实际内容,递归进行
  4. 删除所有的注释行
  5. 添加行号和文件名标识以供编译器使用
  6. 保留所有的 #pragma 指令,因为编译器要使用
file1.i

file1.i 文件里面东西非常多,因为他把<iostream>的代码加进来了,然后我们在文件最后面,发现有coordin.h和file1.cpp中的代码。

编译

g++ -S .\file1.i -o file1.s

file1.s 里面是x86-64汇编代码.

汇编

g++ -c .\file1.s -o file1.o

汇编阶段是把 汇编代码变成机器码,当然用记事本看肯定是乱码,得用专门的二进制文件查看器。

链接

如果我们直接用file1.o生成可执行文件,发现会出错:

这是因为file1.cpp中用到的函数的定义是在 file2.cpp中的。

所以同样的方法我们现生成file2.cpp的.o文件 file2.o

我们可以把file2.o 先生成静态库文件,然后再链接。(当然也可以两个 .o 文件链接)

ar -rcs libfile2.a file2.o
libfile2.a

可以对比一下 静态库文件libfile2.a 和file2.o文件,我们发现库文件就是.o文件前面加了一些代码(索引)

最后我们把file1.o和libfile2.a链接一下,生成可执行文件。

g++ -g .\file1.o .\libfile2.a -o myfile.exe

单独编译

一般来说,C++程序会分为 头文件和源代码文件。
C++鼓励我们将组件函数放在单独的文件中,这就意味着会存在多个源代码文件,C++鼓励我们对这些源代码文件单独编译,然后将这些文件的编译版本链接。
单独编译后在链接的意义在于:如果我们要对一个源文件修改,我们就可以只对这个源文件编译,然后和其他文件的编译版本链接。

头文件

头文件是.h文件
头文件中常包含的内容有:

  • 函数原型
  • 宏 和 const变量
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数
    有些程序员喜欢在头文件中使用include<>include"",为了避免同一个头文件被include多次,就会有:
#ifndef aa
#define aa

#endif

这个代码的意思是,在include这个头文件的时候,如果没有导入过这个头文件,那就导入。

源代码文件

源代码文件是.cpp文件
源代码文件会有函数的定义和main函数等。
在源代码文件中 一般都会使用include<>include"",可以导入.h文件,也可以导入.cpp文件

如何用命令行编译多个源文件?

g++ 参数列表.

  1. -g 编译带调试信息的可执行文件

    -g 选项告诉 GCC 产生能被 GNU 调试器GDB使用的调试信息,以调试程序。

产生带调试信息的可执行文件test
g++ -g test.cpp

  1. -O[n] 优化源代码

    所谓优化,例如省略掉代码中从未使用过的变量、直接将常量表达式用结果值代替等等,这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。

-O 同时减小代码的长度和执行时间,其效果等价于-O1
-O0 表示不做优化
-O1 为默认优化
-O2 除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等。
-O3 则包括循环展开和其他一些与处理特性相关的优化工作。
g++ -O2 test.cpp

  1. -l(小写L)指定库文件和 -L | 指定库文件路径

    -l参数(小写)就是用来指定程序要链接的库,-l参数紧接着就是库名

在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接
链接glog库 g++ -lglog test.cpp
如果库文件没放在上面三个目录里,需要使用-L参数(大写)指定库文件所在目录
-L参数跟着的是库文件所在的目录名
链接mytest库,libmytest.so在/home/bing/mytestlibfolder目录下
g++ -L/home/bing/mytestlibfolder -lmytest test.cpp

  1. -I (大写i)指定头文件搜索目录

    /usr/include目录一般是不用指定的,gcc知道去那里找,但 是如果头文件不在/usr/icnclude里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude 参数了,如果不加你会得到一个”xxxx.h: No such file or directory的错误。-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。

g++ -I/myinclude test.cpp

  1. -Wall 打印警告信息

    打印出gcc提供的警告信息

g++ -Wall test.cpp

  1. -w 关闭警告信息

    关闭所有警告信息

g++ -w test.cpp

  1. -std=c++11 设置编译标准

    使用 c++11 标准编译 test.cpp

g++ -std=c++11 test.cpp

  1. -o 指定输出文件名

    指定即将产生的文件名

指定输出可执行文件名为test
g++ test.cpp -o test

  1. -D 定义宏

    -D 参数在实际开发中还是经常使用的,比如可通过定义不同的宏,实现选择性编译,编译时定义不同的宏就编译相对应的代码。

    #include<iostream>
    using namespace std;
    int main(){
    #ifdef DEBUG
    cout<<"hello ";
    #endif
    cout<<"world!"<<endl;
    }

    g++ -D DEBUG -o test ./test.cpp
    执行test.exe后得到的是hello world!

总结来说,
g++ -L 库文件目录 -l 库文件 -I 头文件搜索目录 -o 指定输出名 1.cpp 2.cpp
例如,file1.cpp放在.\中,file2.cpp放在.\b\中,头文件在.\include
则我们编译这两个文件可以使用
g++ -o mytest -I .\include\ .\file1.cpp .\b\file2.cpp

具体的单独编译细节且看我写的这篇文章:
C++_从源码到可执行文件

vscode中如何设置单独编译,只需修改tasks.json中的args:[]参数就行了

"args": [    //方括号里是传给gcc的参数
"${file}", //指定要编译的是当前文件
"-o", //指定输出文件的路径和名称
"${fileDirname}\\bin\\${fileBasenameNoExtension}.exe",//承接上一步的-o,让可执行文件输出到源码文件所在的文件夹下的bin文件夹内,并且让它的名字和源码文件相同
"-I",//头文件(自己写的)
"",//头文件所在文件夹
"-lfunc",//外部库的名字
"-L./",//外部库的路径
"-g", //生成和调试有关的信息
"-Wall", // 开启额外警告
"-static-libgcc", // 静态链接libgcc
"-fexec-charset=GBK", // 生成的程序使用GBK编码
"-std=c++17", // 语言标准
]

也可以参照其他博主写的这篇
征服VS Code(1):C/C++单文件编译