文中使用软件的云盘下载地址:https://www.aliyundrive.com/s/juMJT9dBZgd。

大多人学习 C 语言会有这样的经历,就是老师让下载一个 IDE,比如 VS 或者 Dev-C++,然后就直接开始写代码了,IDE 确实很方便不过这些是在 Windows 下很舒服,直接打开就可以写,但是随着学习和工作变化,你会发现你不仅会使用到 Windows,甚至在大多数生产环境下使用的都是 Linux,这就很难受了,linux 系统在某些特殊情况你使用的是不带有图行化的界面的发行版 Linux(Ubuntu 就是带有桌面系统的,有些 Debian 系统没有),如下图如所示:

930e0cb79e33fc5b

上图使用的就是不带有图形界面的 Ubuntu(WSL2),这样的情况在嵌入式开发中很常见,甚至对于 Web 开发有些服务器上的 Linux 也是完全不带有图形界面的,这样就谈不上使用 IDE 了,在这样的环境下你该如何编写一个 C 语言程序,并将它编译呢?

为了解决这些问题我们不妨先去了解一下 GNU/Linux 到底是什么?

大名鼎鼎的 GNU/Linux

如果你看过稚晖君的一篇文章,是关于 STM32 开发环境配置,但是不同于以往的 IDE,稚晖君使用的是 Clion,在配置过程中他下载了一个很特殊的配置软件叫 MinGW:

image-20220811010143560

你可能会照着做做,但是不理解这些东西都是干什么的?或者说不理解这些东西是为什么而存在的,在理解之前我们不妨了解一下什么是 GNU。

GNU

GNU 操作系统起源于GNU 计划,由理查德·斯托曼麻省理工学院人工智能实验室发起,希望发展出一套完整的开放源代码操作系统来取代 Unix,计划中的操作系统,名为 GNU。

246px-Heckert_GNU_white.svg

GNU 的商标是一个牛羚头。原先版本是 Etienne Suvasa 所设计,而今比较流行的粗体版本则是 Aurelio Heckert 所设计。

GNU是一个自由操作系统,其内容软件完全以GPL方式发布。这个操作系统是GNU 计划的主要目标,名称来自 GNU's Not Unix!的递归缩写,因为 GNU 的设计类似Unix,但它不包含具著作权的 Unix 代码。

你可能会难以理解,我从来没听说过 GNU 操作系统,像是 Linux,Unix 这种都是很有名的,但 GNU 到底是什么?

1983 年 9 月 27 日,理查德·斯托曼在 net.unix-wizards 和 net.usoft新闻组中公布这项 GNU 计划。斯托曼的目标是成立一个完全自由的操作系统,他希望电脑用户是能够“自由使用”的。因为在 20 世纪 60 年代和 70 年代 - 大多数人都能自由学习软件的源代码,自由地与他人分享的软件,可自由修改软件的行为,自由发布的软件的修改后的版本。也就是说早些年软件几乎都是免费的,每个人都可以自由地使用和修改。

1984 年 1 月 5 日,正式开始开发软件,为了防止这些软件将来可能被主张所有权,影响到自由软件的发展,斯托曼辞去了在实验室的工作。

理查德·斯托曼使用不兼容分时系统(英语:Incompatible Timesharing System) (ITS)[6](一种早期的操作系统,使用汇编语言撰写,因其所运行的称为 PDP-10的电脑系统架构停止发展而变得过时)的经验,导致了需要一种可移植系统的决定。

因此,会议决定,将开发新的系统,并使用 CLisp作为系统编程语言。[8] 且 GNU 将与 UNIX 兼容。[9] 当时,UNIX 已经是一个流行的专有操作系统。而 Unix 的设计是模块化的,所以它可以被逐步分块的实现。

Unix 在学术机构和大型企业中得到了广泛的应用,当时的 UNIX 拥有者AT&T公司以低廉甚至免费的许可将 Unix 源码授权给学术机构做研究或教学之用,许多机构在此源码基础上加以扩展和改进,形成了所谓的“Unix 变种”,这些变种反过来也促进了 Unix 的发展。

后来AT&T意识到了 Unix 的商业价值,不再将 Unix 源码授权给学术机构,并对之前的 Unix 及其变种声明了著作权权利。BSD 在 Unix 的历史发展中具有相当大的影响力,被很多商业厂家采用,成为很多商用 Unix 的基础。其不断增大的影响力终于引起了 AT&T 的关注,于是开始了一场持久的著作权官司,由此人们意识到我们需要一个完全免费的操作系统。

1984 年,开始发展编辑器Emacs等软件。1985 年,发表GNU 宣言。1989 年,发表GNU 通用公共许可协议。这个公共开源许可协议,你如果开源过项目一定见过,他叫 GPL 协议,GPL 是自由软件开源软件的最流行许可证[18]。到2004年4月,GPL已占Freshmeat(英语:Freshmeat)上所列的自由软件的约75%,SourceForge的约68%。类似的,2001年一项关于Red Hat Linux 7.1 的调查显示一般的代码都以 GPL 发布。著名的 GPL 自由软件包括 EMACS,Linux核心(并非所有Linux 发行版的核心都是开源的)和GCC

我们回到 GNU 的操作系统来,因为许多必要的软件需要从零开始写起来,但是有很多已经存在的第三方组件如 TeXX Window System[4]Mach微内核等等都可以进行利用,其中GNU最关键系统内核,是在GNU Mach微内核基础上进行开发,理查德·马修·斯托曼认为可以借此加速操作系统的开发,但因为一直不确定卡内基梅隆大学何时要将核心源代码发布,造成计划延宕三年。他在之后承认这是个错误。

下图是大佬的照片:

Richard_Stallman_at_LibrePlanet_2019

除了上述的第三方组件外,大多数的 GNU 软件是由许多志愿者,在他们的空闲时间,或由公司、教育机构和非营利性组织赞助下撰写。

到 1990 年代初期,操作系统中所需的许多程序(例如库、编译器文本编辑器命令行 shell窗口系统)已完成,尽管低级元素如称为GNU Hurd 的设备驱动程序守护进程内核都停滞不前且不完整。

1991 年,Linux 出现。1993 年,FreeBSD发布。FreeBSD 就是当年与 Unix 著作权持有公司达成协议,重写的免费版 Unix,所有 GNU 计划中,运行于用户空间的软件,都可以在 Linux 或 FreeBSD 上使用。许多开发者转向于 Linux 或 FreeBSD。其中,Linux 成为常见的 GNU 计划软件运行平台。理查德·斯托曼主张,Linux 操作系统使用了许多 GNU 计划软件,应正名为GNU/Linux,但没有得到 Linux 社群的一致认同,形成GNU/Linux 命名争议

林纳斯·托瓦兹(Linus)开始在MINIX上开发Linux 内核,为MINIX写的软件也可以在Linux 内核上使用。[16]后来使用GNU软件代替MINIX的软件,因为使用从GNU系统来的源代码可以自由使用,这对Linux的发展有益。使用GNU GPL 协议的源代码可以被其他项目所使用,只要这些项目使用同样的协议发布。

对于 GNU 来说没有构成自己的操作系统是失败的,但是这个失败确实促进了行业进步与发展,林纳斯·托瓦兹曾说过如果 GNU 内核在 1991 年时可以用,他不会自己去写一个。实际上现在 GNU 代指的是当年发展下来的一系列软件。

GNU 的组成

该系统的基本组成包括 GNU 编译器套装(GCC)、GNU 的 C 库(glibc)、以及 GNU 核心工具组(coreutils),另外还有 GNU 调试器(GDB)、GNU编程语言工具程序(binutils)、GNU Cash shell 中[10] 和 GNOME 桌面环境。GNU 开发人员已经转向 GNU 应用程序和工具的 Linux 移植 ,现在也广泛应用在其它操作系统中使用,上文我们提到的 MinGW 实际上叫 Minimalist GNU for Windows,也就是向 Windows 移植的 GNU 最小套件,是将GCC编译器和GNU Binutils移植到 Win32 平台下的产物,包括一系列头文件(Win32API)、可执行文件。另有可用于产生 32 位及 64 位 Windows 可执行文件的MinGW-w64项目,是从原本 MinGW 产生的分支。

一点补充

Binutils指的是一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size 等。这一组工具是开发和调试不可缺少的工具。

  • addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
  • as:主要用于汇编。
  • ld:主要用于链接。
  • ar:主要用于创建静态库。
  • ldd:可以用于查看一个可执行程序依赖的共享库。
  • objcopy:将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或者将.elf 转换成.bin 等。
  • objdump:主要的作用是反汇编。
  • readelf:显示有关 ELF 文件的信息。
  • size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等。

MinGW

GCC 支持的语言大多在 MinGW 也受支持,其中涵盖CC++Objective-CFortranAda。对于 C 语言之外的语言,MinGW 使用标准的 GNU运行时库,如 C++使用 GNU libstdc++

但是 MinGW 使用 Windows 中的 C运行时库。因此用 MinGW 开发的程序不需要额外的第三方DLL支持就可以直接在 Windows 下运行,而且也不一定必须遵从GPL许可证。这同时造成了 MinGW 开发的程序只能使用 Win32API 和跨平台的第三方库,而缺少 POSIX 支持,大多数 GNU 软件无法在不修改源代码的情况下用 MinGW 编译。

运行时库

谈到运行时库,就不得不在讨论一下 C 标准,C 标准主要由两部分组成,一部分描述 C 的语法,另一部分描述 C 标准库(描述了一些 C 标准函数的原型,但是不提供实现)。 C 标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型声明和宏定义。常见的 C 标准就是ANSI C;为了提高 C 语言的开发效率,C 标准定义了一系列常用的函数,称为C 标准库函数。应用程序开发者可以包含这些标准函数的头文件,来调用这些 C 标准函数,来开发应用,这样就可以屏蔽平台的差异;

C 标准库函数的实现留给了各个系统平台;这个实现就是C 运行时库(C Run Time Libray) ,简称 CRT;C 运行库,是和平台相关的,即和操作系统相关的;C 运行库(CRT)从某种程度上来讲是C 语言的程序和不同操作系统平台之间的抽象层;接口是统一的标准,实现由各个平台自己实现;Linux 和 Windows 平台下的两个主要 C 语言运行库分别为 glibc(GNU C Library)和 MSVCRT(Microsoft Visual C Run-time)。也就是说你如果在 Linux 下就可以使用 glibc,如果使用 Windos 下的 Microsoft Visual C 俗称 VC IDE,就是用的是MSVCRT

值得注意的是,像线程操作这样的功能并不是标准的 C 语言运行库的一部分,但是 glibc 和 MSVCRT 都包含了线程操作的库函数。所以 glibc 和 MSVCRT 事实上是标准 C 语言运行库的超集,它们各自对 C 标准库进行了一些扩展。也就是说 CRT 实际上包含两部分,一部分实现是基于 C 标准库来的,一部分实现是根据平台自身开发的库

某种程度上是C 运行库是 C 标准库的一个扩展库,加了很多 C 标准库所没有的与平台相关的或者不相关的库接口函数。要在一个平台上支持 C 语言,不仅要实现符合平台的 C 编译器,还要实现 C 标准库,这样的实现才算符合 C 标准。

额外的内容

MinGW 使用 Windows 中的C 运行时库,也就是默认链接到 Windows 操作系统组件库MSVCRT,这句话其实还有更深一层含义,如果你涉及过 windos 下的 UI 界面开发就会知道还有一个东西叫Windows API,C 语言要早于 Windows 出现,而且 C 语言实际标准制定的开始时间也要早于 Windows(API 概念出现的)系统的开发时间。所以 Windows 系统在开发的时候是完全可以使用 C 语言的。目前最多的说法是用 C 和汇编实现的。那么只要用 C,就可能用 C 标准库。

一般情况下,我们说 C 运行库暗含的意思是哪种平台哪个开发平台的 C 运行库,CRT 的实现是基于 Windows API 的,而 WindowsAPI 的开发也是基于 C 语言的,但不是或者不一定基于 CRT(或者 C 标准库)的。

再深一步,虽然 CRT 是基于操作系统 API 实现的,但并不代表所有的 CRT 封装了操作系统 API,如一些用户的权限控制,操作系统线程创建等都不属于 C 运行库,于是对于这些操作我们就不得不直接调用操作系统 API 或者其他库。

总结一下,C 标准库就是任何平台都可以使用的基本 C 语言库。而 CRT 除了将 C 标准库加入所属范围外,还扩展了与平台相关的接口库,这些接口实现根据不同平台调用不同平台的操作系统 API,如下图所示,采用 C 标准库编写的程序可以应用到 windows 平台,也可以应用到 linux 平台;而用 CRT 另外与平台相关的库函数编写的应用程序不能跨平台运行。

417858-20151208212950230-563268666

我们接下来安装 MinGW 并使用目前全宇宙最好的文字编辑器,编写 HelloWorld。最后我们再迁移到更舒服的 Clion 平台上,并完成环境配置。

MinGW 的环境配置安装

地址如下:https://sourceforge.net/projects/mingw/。

点击下载以后,会打开一个 Mingw 的安装器的安装,如下图:

image-20220811172912384

打开 exe 进行安装,修改安装目录(最好不能有空格),安装完成后进行组件下载:

这一部分就不详细介绍,有兴趣的可以看稚晖君的博客安装,我们在这里使用最新的 MinGW64 来配置,我个人是两个版本都进行了安装。

因为新版的 MinGW64 是完全开源的,只提供源码,需要你自己编译,不过我们还可以通过MSYS2 这个工具来下载新版的 MinGW64。

MSYS2是一组工具和库,为您提供易于使用的环境,用于构建、安装和运行本机 Windows 软件。

它由一个名为 mintty的命令行终端、bash、git 和 subversion 等版本控制系统、tar 和 awk 等工具,甚至 autotools 等构建系统组成,所有这些都基于Cygwin的修改版本。尽管其中一些核心部分基于 Cygwin,但 MSYS2 的主要重点是为本地 Windows 软件提供构建环境,并且使用 Cygwin 的部分保持在最低限度。MSYS2 为 GCC、mingw-w64、CPython、CMake、Meson、OpenSSL、FFmpeg、Rust、Ruby 等提供最新的原生构建。

准确的说是集成了pacmanMingw-w64 的Cygwin升级版, 提供了bash shell 等linux环境、版本控制软件(git/hg)和 MinGW-w64 工具链。简单的来说模拟 Linux 运行环境的技术,它的一个优点就在于利用 pacman 包管理器,我们可以比较轻松的使用 Linux 包管理器的方式来安装一整套可以在 Windows 上运行的 Linux 工具。如果你只是想要在 Windows 上简单运行一些 Linux 程序,那么 msys2 是一个很好的选择。不过我们这里不是想要完全模拟 Linux 而是我们想通过 MSYS2 来获取 MinGW 的工具链。

软件已经放到了开头的网盘里面,安装过程如下:

image-20220811195040080

一路下一步:

image-20220811192435483

image-20220811195040080

这时候会打开一个终端,你会很眼熟这不是 git 那个 bash 吗?

image-20220811200409049

我们首先更新包数据库和基础包。

使用命令:pacman -Syu

image-20220811200945364

重新开启后记得在运行一下,完成安装。

image-20220811201222789

更新完成后如下:

image-20220811201839522

使用 pacman 的命令 安装 Mingw-W64 全部工具链。下图你可以看到他把所有的工具都给你列出来了。

image-20220811202116824

这里可能需要网络条件好一点,推荐一个魔法,便宜好用:链接地址

下载完成:

image-20220811203652380

安装完成以后可以在开始栏里搜到一个神奇的东西:

image-20220811203757485

在系统环境变量中添加如下内容:

如果使用的是 MinGW32,同样也需要把对应的内容放到环境变量里面。我两个都进行了安装,整体十分方便,基本 20mins 就搞定了。

image-20220811205451002

我们在命令行中测试一下。

image-20220811204508163

在网盘里面你也可以找到 VScode 这里我就不再赘述,直接安装即可。

我们新建一个文件夹 C++,还有一个 Projects 文件夹和 HelloWorld 文件夹,右键通过 code 打开 Helloworld 文件夹。

image-20220811210545715

注意下图是错误的,应该打开对应文件夹,为不是文件,因为 VScode 需要加入一些调试设置和编译指令:

image-20220811205855372

然后安装 C/C++的插件:

当然你也可以不安装直接调用 gcc 进行编译不过我们为了方便还是安装插件进行调试。插件为我们提供了 debug 和自动补齐功能:

image-20220811210137132

我们将鼠标悬停到对象上就会有对象的详细介绍。

image-20220812003427961

想要运行的时候,就点击右上角的运行按钮:

image-20220811212420223

这时候会让你选择编译器:

image-20220811211056810

选择完成以后他会直接编译并且运行:

image-20220811211303051

同时调试控制台也会输出 GDB 的信息,你会亲切的看到一个内容他叫 GNU,由此你的 C++之路就正式开始了。

image-20220811212533529

如果你仔细了解就会发现,你在选择完编译器以后,VSCode 会产生一个文件夹,这是 VSCode 保存项目信息的文件夹,本着刨根问底的精神我们再来看看这个 json 文件是干什么的:

{
  "tasks": [
    {
      "type": "cppbuild",
      "label": "C/C++: g++.exe 生成活动文件",
      "command": "C:\\msys64\\mingw64\\bin\\g++.exe",
      "args": [
        "-fdiagnostics-color=always",
        "-g",
        "${file}",
        "-o",
        "${fileDirname}\\${fileBasenameNoExtension}.exe"
      ],
      "options": {
        "cwd": "${fileDirname}"
      },
      "problemMatcher": ["$gcc"],
      "group": {
        "kind": "build",
        "isDefault": true
      },
      "detail": "调试器生成的任务。"
    }
  ],
  "version": "2.0.0"
}

tasks 指的是当你点下按钮的时候会去执行的任务, "command"设置指定了要运行的程序;在本例中是 g++。args指定了将被传递给 g++的命令行参数。这些参数必须按照编译器所期望的顺序指定。

这个任务告诉 g++接收活动文件( ${file}),对其进行编译,并在当前目录( $ {fileDirname})下创建一个可执行文件,其名称与要编译的源码文件名称相同,但扩展名为.exe(${fileBasenameNoExtension}.exe),在我们的例子中产生其实产生了 helloworld.exe。

"label"值是你将在任务列表中看到的;你可以随心所欲地命名它。

"detail"是你将在任务列表中作为任务的描述。强烈建议重命名这个值,以便将它与类似的任务区分开来。

从现在开始,播放按钮将从tasks.json中读取信息以确定如何构建和运行你的程序。你可以在tasks.json中定义多个编译任务,哪个任务被标记为默认任务,播放按钮就会使用哪个。如果你需要改变默认的编译器,你可以运行 Tasks: 配置默认构建任务。或者,你可以修改 tasks.json 文件,通过替换以下这一段来删除默认任务。

    "group": {
        "kind": "build",
        "isDefault": true
    },

把上述任务就可以替换成:

 "group": "build",

这样这项任务就不再是默认任务了,执行的时候如果存在多个任务就会让你选择:

image-20220811220647393

如果你想要将这个文件夹下的所有 CPP 文件进行编译也很简单,我们直接修改 args 中的参数为:

"args": [
    "-fdiagnostics-color=always",
    "-g",
    "${workspaceFolder}/*.cpp",\\修改这里
    "-o",
    "${fileDirname}\\Jszszzy.exe"
]

或者你想要修改对应的输出文件的名称,也很简单,修改对应参数如下:

"args": [
    "-fdiagnostics-color=always",
    "-g",
    "${workspaceFolder}/*.cpp",
    "-o",
    "${fileDirname}\\Jszszzy.exe"\\修改这里
]

当然以上这种方式是最原始的,我们还有 make、cmake 等工具还没有介绍,以后有时间再详细介绍,此篇作为我 C++的开篇,搭建好了一个 C++的学习使用环境,随着时代变迁比如 Clion 这种内置 CMake 的 IDE 已经出现,更适合大工程,大项目,不过为了学习我们还是使用这种原汁原味的更加深理解。

关于 Task 的网上有很多介绍,这里不再多说,详情见链接

我在这里简单演示一下如何同时编译处于两个不同文件夹的源码文件

文件结构如下:

image-20220811234156172

task.json 配置如下:

{
  "tasks": [
    {
      "type": "cppbuild",
      "label": "build1",
      "command": "C:\\msys64\\mingw64\\bin\\g++.exe",
      "args": [
        "-fdiagnostics-color=always",
        "-g",
        "${workspaceFolder}/1/*.cpp",
        "-o",
        "${workspaceFolder}/1\\1.exe"
      ],
      "options": {
        "cwd": "${workspaceFolder}/1"
      },
      "problemMatcher": ["$gcc"],
      "detail": "调试器生成的任务1。"
    },
    {
      "type": "cppbuild",
      "label": "build2",
      "command": "C:\\msys64\\mingw64\\bin\\g++.exe",
      "args": [
        "-fdiagnostics-color=always",
        "-g",
        "${workspaceFolder}/2/*.cpp",
        "-o",
        "${workspaceFolder}/2\\2.exe"
      ],
      "options": {
        "cwd": "${workspaceFolder}/2"
      },
      "problemMatcher": ["$gcc"],
      "detail": "调试器生成的任务2。"
    },
    {
      //通过dependsOn来将两个任务合成一个任务
      "label": "build",
      "dependsOn": ["build2", "build1"],
      "problemMatcher": ["$gcc"],
      "group": {
        "kind": "build",
        "isDefault": true //设为默认任务
      },
      "presentation": {
        "echo": true,
        "reveal": "always",
        "focus": true, //这个就设置为true了,运行任务后将焦点聚集到终端,方便进行输入
        "panel": "new"
      },
      "command": "2/2.exe" //执行第二个任务
    }
  ],
  "version": "2.0.0"
}

使用命令:

image-20220811233756141

最后执行第二个任务:

image-20220811233820750

参考文章如下:

https://code.visualstudio.com/docs/cpp/config-mingw

https://www.zhihu.com/question/333560253

https://zh.wikipedia.org/wiki/C%2B%2B%E6%A8%99%E6%BA%96%E5%87%BD%E5%BC%8F%E5%BA%AB

https://zh.wikipedia.org/wiki/MinGW

https://www.cnblogs.com/renyuan/p/5031100.html