参考文档
原视频
一组编译器套件,并非单独的一个编译器。包含C/C++、Objective-C、Java、Go等语言的编译器,以及这些语言的库(e.g. libstdc++、libgcj)
gcc/g++:gcc 和 GCC 是两个不同的东西,gcc 是 GCC 中的 GNU C Complier(C编译器),而 g++ 是 GCC 中的 GNU C++ Complier(C++编译器)。
而究其本质,gcc/g++ 不是编译器,只是一种驱动器,根据参数中要编译的文件类型(.c .cpp)调用相应的GNU编译器。
因此,使用 gcc 编译 c++ 文件,需要手动加参数 -lstdc++
以使用 STL,但并非gcc -lstdc++
与g++
等价。
基于LLVM
传统编译器架构:
LLVM:一种新式编译器架构,它的 前端 和 后端 是模块化的,而所有的前端通过同一个LLVM优化器,再分别链接不同的后端。
比如,Clang 就是 LLVM 对于 C/C++ 的Frontend,对于其它语言来说,也都有各自对应的前端。而针对不同的体系结构如 x86、arm,也有相应的 LLVM for x86/arm Backend。
对于传统编译器如 GCC 来说,每一种语言都需要一个前端、一个后端、一个优化器,前后端高耦合,扩展性显而易见地弱于 LLVM。
相较于 GCC,Clang还具有以下优点:
Microsoft Visual C++,即 MSVC,是 Windows Visual Studio 的一部分,是其中C、C++ 和汇编语言的开发工具和库
可以同时安装 VS2015/17/19 三个版本,它们编译器大版本都是14
Visual Studio版本 | Visual C++ 版本 | C++编译器版本 |
---|---|---|
VS2015 | msvc-140 | v140 |
VS2017 | msvc-150 | v141 |
VS2019 | msvc-160 | v142 |
Q:为什么不同的处理器需要不同的C++编译器?
A:不同的CPU体系结构(x86、x64、arm)具有不同汇编指令集,而编译器恰恰是把C++代码翻译成汇编代码的工具。
Q:为什么不同的操作系统厂商也在发展各自的编译器?
A:虽然C++标准库是一样的,但是在不同操作系统上的实现不同,因为需要适配不同操作系统的自有头文件和动态库等。(e.g. windows sdk、win32 API、linux-xx-dev)
Q:What is GNU?
A:GNU is Not Unix,是自由软件基金会发起的一项计划,这个计划中包含许许多多的软件,GCC 编译器是其中之一,而Linux系统本身也是一个GNU软件。GNU 规定了这些软件需要遵守的一些协议条款,满足这些协议的软件就被视为GNU软件,其中最著名的就是GPL协议。其实,70年代出现的UNIX 作为一种商业用操作系统不开源、不免费(e.g. solaris)。因此1985年,Richard Stallman创立自由软件基金会为GNU计划提供支持,GNU的最终目标就是开发一套开源免费的操作系统,并设计了开源免费的内核Hurd。当时的计算机并没有各种各样的io设备,所谓的操作系统基本等于内核。Linus Torvalds在1991年设计出了开源、免费的Unix-like的内核Linux,并且由于其Unix-like的特性,使其可以兼容许多Unix软件,相较于Hurd优势巨大。而Linux的开源协议,恰恰就是GPL。在开源过程中,GNU中的许多工具(GNU包含许多软件,且均开源,这些软件中有一些工具性质的软件,比如GCC)被集成到Linux平台。最后,GNU的计划也算成功实现了——虽然Hurd失败了,但Linux成功了。Linux的发行版,指的是使用Linux内核,并做了一些其它功能的操作系统软件。
MinGW
cmake
win + R
呼出 cmd,输入g++ --version
查看版本信息,显式 GCC 则说明配置成功cmake --version
查看版本信息Q:What is Cmake?
A:Cmake 是一款快捷生成 makefile 的工具,makefile 是编译大型软件前我们提前设置好的编译规则,然而 makefile 的编写并不容易,使用 Cmake 可以大大简化编写 makefile 的工作量
ctrl + alt + n
运行代码,计划通run in terminal
,找到 Code Runner 对应的选项,打勾,这样在终端运行时就不再是只读权限,我们可以向程序输入内容了ctrl + shift + p
搜索JSON,打开C/C++:JSON,将c_cpp_properties.json
中的内容更改如下{
"configurations": [
{
"name": "Win32",
"includePath": [
"C:\\MinGW/**" //include头文件的路径,/**表示包含该目录下所有文件
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:\\MinGW\\bin\\g++.exe", //编译器路径
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-gcc-x64" //修改编译器路径后保存,系统会生成提示
}
],
"version": 4
}
c_cpp_properties.json
文件,更好的做法是打开VScode的setting.json
,在大括号内部添加如下代码,添加后当我们再度打开c_cpp_properties.json
文件,就会发现相关的内容从c_cpp_properties.json
中消失了,因为都已经在setting.json
中配置好了"C_Cpp.default.cppStandard": "c++17",
"C_Cpp.default.cStandard": "gnu17",
"C_Cpp.default.compilerPath": "C:\\MinGW\\bin\\g++.exe",
"C_Cpp.default.includePath": [
"C:\\MinGW/**"
],
"C_Cpp.default.intelliSenseMode": "windows-gcc-x64"
原视频
g++ .\main.cpp
编译 .cpp 文件,这里输入g++ main
后直接按tab
就可以自动补全.\a.exe
执行这个文件,同样也是输入a
后直接按tab
自动补全ls
,查看当前新建文件夹内的文件,会发现除了显示 main.cpp 和 a.exe 以外,还会显示它们的文件大小,我们直接使用 g++ 命令编译得到的可执行文件内部不包含调试信息,无法进行调试g++ -g .\main.cpp -o test
,其中-g
代表包含调试信息,-o
为自定义 exe 文件名称,那么,左侧就会生成一个新的可执行文件 test.exe,再次输入ls
发现 test.exe 的大小更大一些F5
对断点进行调试,在 json 文件生成后,当前文件夹内就会自动生成 main.exe 文件,我们打好断点就能进行调试,正是因为这个文件的存在"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
和"preLaunchTask": "C/C++: g++.exe 生成活动文件"
"program"
就是我们需要调试的 .exe 文件,生成可执行文件前必需的编译和链接,都由 VScode 自动完成"preLaunchTask"
则代表着调试前 VScode 需要做的工作,这些信息被包含在 task.json 中,也是VScode 自动生成的- 正因为 launch.json 包含了这些信息,因此我们只需要生成一个 launch.json 文件就可以对单个可执行文件进行调试
void swap( int& a, int& b );
(注意函数声明后面的分号),在 swap.cpp 和 main.cpp 中包含我们自定义的这个头文件#include "swap.h"
(包含自定义头文件用双引号)g++ -g .\main.cpp .\swap.cpp -o test
,将多个 .cpp 文件编译成一个可执行文件 test.exe"preLaunchTask": "C/C++: g++.exe 生成活动文件"
,并且并未像单文件调试一样,生成一个默认的 a.exe
- 打开 launch.json ,找到
"cwd"
,代表当前文件夹的绝对路径,将它的值${fileDirname}
拷贝到"program"
中,并添加上面编译完成的 .exe 文件名,修改后即"program": "${fileDirname}/test.exe",
,这行代码将 launch.json 与我们手动编译的 test.exe 绑定,表示我们接下来要调试的文件是 test.exectrl + /
注释掉"preLaunchTask"
,ctrl + s
保存,再回到 main.cpp 中,断点调试,成功
project(swapfunc)
,括号里为项目的名称add_executable(swap_cmake main.cpp swap.cpp)
,swap_cmake 是可执行文件的名称,后面是我们需要编译的两个 .cpp 文件,最简单的 cmakelist 就这样写完了ctrl + shift + P
,输入 cmake,点击 Cmake: Configure,选择我们需要的编译器 GCC,此时在最下方的状态栏会显示当前编译器版本为 GCC,与此同时,当前文件夹下自动生成 build 文件夹,用于 cmake 进行外部构建cd .\build\
,再输入cmake ..
(有个空格),显示 -- Configuring done -- Generating done
,说明一切正常min
,按下tab
补全为mingw32-make.exe
(这是windows中make的名字,Linux中不是这个名字),然后执行,在 build 文件夹中顺利生成可执行文件 swap_cmake.exe
- make执行后,生成的第一行和第二行代码是将两个 .cpp 文件编译成二进制文件
[33%] Building CXX object CMakeFiles/swaptest.dir/main.cpp.obj
[66%] Building CXX object CMakeFiles/swaptest.dir/swap.cpp.obj
- 第三行代码完成链接
[100%]Linking CXX executable swap_cmake.exe
我们删掉 build 文件夹来尝试手动创建,在终端一次输入
mkdir build
,cd build
,cmake ..
,可能会出现一个小问题:如果安装了 VS,可能会调用 MSVC
解决的办法是输入cmake -G "MinGW Makefiles" ..
,将编译器切换成 MinGW 即 GCC 并完成编译,以后正常使用cmake ..
即可(需要提前删除 MSVC 生成的 makefile,可以直接清空 build 文件夹,命令行指令是rm
)
"program"
选项为 swap_cmake.exe 所在的路径 ,得到"program": "${workspaceFolder}/build/swaptest.exe"
,注释掉 "preLaunchTask"
,回到 main.cpp,断点调试成功"preLaunchTask"
,因此也就没有 task 文件,并且需要修改"miDebuggerPath"
的值为"C:\\MinGW\\bin\\gdb.exe"
,这个路径找到了 MinGW 文件夹内的 gdb.exe,注意这里描述路径用的是" \\",而非 windows 惯常使用的" /"(我又将"name"
的值改为g++.exe
,不清楚不改是否有影响),改好后断点调试成功mingw32-make.exe
,这次只重新编译 main.cpp 和链接,没有编译我们并未改动的 swap.cpp"preLaunchTask"
(当然在 cmake 编译后生成的 json 没有这个对象),会报错,这个错误和当时我们不修改 json 而直接进行多文件调试所汇报的错误是一样的,此时需要我们新建一个task
- launch.json ——for debug
- task.json——for build before debug
ctrl + shift + P
,输入 task,点击 Tasks: Configure Task ,会生成一个文件(里面都是别名,可读性差),我们使用 cmake 编译后再生成的 launch.json 里面是没有 task.json 的,因此不能实现我们后面提到的重要功能"preLaunchTask": "C/C++: g++.exe build active file",
(注意这个逗号!),之后在 main.cpp 内断点调试,报错,点击生成任务,选择第三项 g++,这样也能生成一个 task 文件,这个 task.json 相较于前面生成的更为直观(内容相同,但是写法上没有乱七八糟的别名,更容易阅读)"command": "C:\\MinGW\\bin\\g++.exe", //g++的地址
"args": [ //参数列表
"-g",
"${file}", //文件列表
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe" //自定义可执行文件的名字
],
也就是说, task.json 实际上就是做了这件事:
g++ -g .\main.cpp .\swap.cpp -o test
,我们不使用 cmake 而手动编译的时候,需要在终端中输入的这样一行代码 [Step5],使用 cmake 后,这件事就交给 cmake 去做了,进行如下改动,则与上述语句一模一样
"command": "C:\\MinGW\\bin\\g++.exe",
"args": [
"-g",
"main.cpp",
"swap.cpp", //注意逗号
"-o",
"${fileDirname}\\test.exe"
],
"label"
必须和 launch.json 里的"preLaunchTask"
保持一致,并且 launch.json 里的"program"
、以及 task.json 里的"command"
的最后一个参数${fileDirname}
也要和我们所调试的那个可执行文件保持一致,(task 生成了可执行文件,launch 指向了这个可执行文件)之后回到 main.cpp,成功断点调试"preLaunchTask"
的功劳,它会先运行 task.json ,之后再调试新生成的可执行文件,我们注释掉这一行,则又无法实现这个功能了,这是 launch.json 里最重要的一个功能!回顾在 [Step 6] 中,我们执行的几个环节
mkdir build
(或通过 Cmake: Configure 自动生成 build 文件夹)cd build
进入 build 文件cmake ..
mingw32-make.exe
开始编译task.json 文件示例如下:
{
"version": "2.0.0",
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++.exe build active file",
"command": "C:\\MinGW\\bin\\g++.exe",
"args": [
"-g",
"main.cpp",
"swap.cpp",
"-o",
"${fileDirname}\\swaptest.exe"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": "build",
"detail": "编译器: C:\\MinGW\\bin\\g++.exe"
}
]
}
现在自己写一个 task.json
{
"version": "2.0.0",
"options": { //cd build
"cwd": "${fileDirname}/build"
},
"tasks": [
{ //task内第一个结构,实现cmake ..
"type": "shell",
"label": "cmake",
"command": "cmake",
"args": [
".." //..实质是参数,因此需要加空格
]
},
{ //第二个结构,实现make,即mingw32-make.exe
"label": "make",
"group": {
"kind":"build",
"isDefault": true
},
"command": "mingw32-make.exe", //command的值如果是make,那只能在Linux下实现
"args": [
]
},
{ //第三个结构,就是把前两个包一下
"label": "Build", //launch.json中的preLaunchTask要与其一致,做相应改动
"dependsOn":[
"cmake",
"make"
]
}
]
}
修改后的 launch.json 示例如下,断点调试成功,可以边改动边调试(我没有成功,不知道哪里出错)
{
"version": "0.2.0",
"configurations": [
{
"name": "g++.exe",
"type": "cppdbg",
"preLaunchTask": "Build", //顾名思义,在launch前需要执行的task
"request": "launch",
"program": "${workspaceFolder}/swaptest.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "C:\\MinGW\\bin\\gdb.exe",
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}