Reference
在开始之前我们先讲讲 C 语言是如何实现跨平台的,C 语言诞生于上世纪 70 年代,那时候距离今天我们熟知的 Linux、Windows 等操作系统诞生还很遥远,但是今天我们仍能在这些操作系统上运行 C 语言,为什么?其实主要依靠的是编译器和标准库。
- 标准库:如
stdio.h
,stdlib.h
等,他们提供了一个抽象层,这些库在不同平台上有特定的实现,但对外提供一致的 API,使得程序可以在不同操作系统上运行时,使用相同的接口和函数。在 Linux 系统上默认的 C 标准库通常是 glibc,Windows 上的则为 MSVCRT 或 UCRT - 编译器:如
gcc
,clang
,MSVC
等等,它们负责将 C 代码编译成特定平台的机器码,不同平台的编译器会处理平台特有的细节,使得同一份代码可以在不同操作系统上编译运行,例如 GCC 在 Linux 上生成 ELF 格式的可执行文件,在 Windows 上生成 PE 格式的可执行文件
今天的我们使用 Windows 学习 C 语言时通常采用 Visual Studio,它使用 MSVC 编译器和 UCRT 库,但是它实在过于庞大,每打开一个文件都要许久,这里介绍另一种轻便的方案:MSYS2 + Visual Studio Code。
1. MSYS2 介绍和安装
1. 简介
MSYS2 is a collection of tools and libraries providing you with an easy-to-use environment for building, installing and running native Windows software.
It consists of a command line terminal called mintty, bash, version control systems like git and subversion, tools like tar and awk and even build systems like autotools, all based on a modified version of Cygwin. Despite some of these central parts being based on Cygwin, the main focus of MSYS2 is to provide a build environment for native Windows software and the Cygwin-using parts are kept at a minimum. MSYS2 provides up-to-date native builds for GCC, mingw-w64, CPython, CMake, Meson, OpenSSL, FFmpeg, Rust, Ruby, just to name a few.
To provide easy installation of packages and a way to keep them updated it features a package management system called Pacman, which should be familiar to Arch Linux users. It brings many powerful features such as dependency resolution and simple complete system upgrades, as well as straight-forward and reproducible package building. Our package repository contains more than 3300 pre-built packages ready to install.
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 等。
为了提供便捷的软件包安装和更新,它提供了一个名为 Pacman 的包管理系统,这对 Arch Linux 用户来说应该很熟悉。它带来了许多强大的功能,如依赖解析和简单的完整系统升级,以及简洁和可重复的包构建。我们的软件包仓库包含了超过 3300 个预构建的软件包,随时可以安装。
简单来说,你可以通过 MSYS2 更方便地使用 gdb、gcc、git 等开源工具,UCRT 等开发库。
2. 安装
通过 MSYS2 Installer 安装:
# 1. 通过 powershell 命令行安装,通过 win + x 快捷键可以找到 powershell
# 2. 建议断网安装,不然容易卡死在 “updating trust database” 处
# 3. & 后填安装包的绝对路径,--root 后填安装位置
& C:/Users/koharu/Desktop/msys2-x86_64-latest.exe in --confirm-command --accept-messages --root C:/msys64
3. 设置环境
MSYS2 包括以下七种环境(最后两个是 32 位环境,已过时):
名称 | 位置(/ 对应 MSYS2 的安装位置,默认为 “C:\msys64”) | 编译器工具链 | 计算机系统架构 | C 标准库 | C++ 标准库 | |
---|---|---|---|---|---|---|
![]() | MSYS | /usr | gcc | x86_64 | cygwin | libstdc++ |
![]() | UCRT64 | /ucrt64 | gcc | x86_64 | ucrt | libstdc++ |
![]() | CLANG64 | /clang64 | llvm | x86_64 | ucrt | libc++ |
![]() | CLANGARM64 | /clangarm64 | llvm | aarch64 | ucrt | libc++ |
![]() | MINGW64 | /mingw64 | gcc | x86_64 | msvcrt | libstdc++ |
![]() | CLANG32 | /clang32 | llvm | i686 | ucrt | libc++ |
![]() | MINGW32 | /mingw32 | gcc | i686 | msvcrt | libstdc++ |
关键在于 UCRT、Cygwin 和 MSVCRT,这是 C 标准库在 Windows 上的三种不同实现 ,它们之间的主要区别如下:
- UCRT (Universal C Runtime): 是 Windows 上较新的 C 标准库实现,作为 Windows 10 以及更新版本的一部分发布,也是 Visual Studio 默认使用的库,旨在提供更一致和可靠的运行时环境,并与 Windows API 紧密集成。
- Cygwin: 是一个大型的开源工具集合,旨在在 Windows 上提供一个类 Unix 环境。它提供了一个 POSIX 兼容层,将 Unix 系统调用转换为 Windows 系统调用。
- MSVCRT (Microsoft Visual C++ Runtime): 是 Windows 上默认的 C 标准库实现,支持大多数 C 标准库功能,但由于太过久远,甚至无法兼容 C99(mingw-w64 提供替代函数,使其能在大多情况下兼容 C99)。
因此我们首选的环境自然是 UCRT64。
# 为了能在 PowerShell 中执行 ps1 脚本,需要先更改执行策略
# Restricted:不允许任何脚本运行(默认)
# RemoteSigned:本地脚本可以运行,远程脚本需要签名
# Unrestricted:允许运行任何脚本
# -Scope 用于制定范围 CurrentUser 表示当前用户范围,LocalMachine 表示系统范围
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
将下面的代码保存为 setMsys2.ps1
,在 PowerShell 中调用即可
# 添加相关环境变量
$envVariables = @{
MSYSTEM = "UCRT64"
UCRT_HOME = "C:\msys64\ucrt64"
C_INCLUDE_PATH = "%UCRT_HOME%\include"
LIBRARY_PATH = "%UCRT_HOME%\lib"
}
foreach ($key in $envVariables.Keys) {
$currentValue = [System.Environment]::GetEnvironmentVariable($key, [System.EnvironmentVariableTarget]::Machine)
if (-not $currentValue -or ($envVariables[$key] -ne $currentValue)) {
[System.Environment]::SetEnvironmentVariable($key, $envVariables[$key], [System.EnvironmentVariableTarget]::Machine)
}
}
# 添加相关路径到 Path
$pathsToAdd = @("C:\msys64", "%UCRT_HOME%\bin")
$machinePath = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::Machine)
$machinePath = $machinePath.TrimEnd(';')
foreach ($path in $pathsToAdd) {
if (-not ($machinePath.Split(';') -contains $path)) {
$machinePath = "$machinePath;$path"
}
}
[System.Environment]::SetEnvironmentVariable("Path", $machinePath, [System.EnvironmentVariableTarget]::Machine)
2. 安装 C & C++ 工具链
完成上面的步骤后,我们可以通过在终端输入
msys2_shell
来打开 MSYS2 环境
1. 软件源
前面提到过:/ 对应 MSYS2 的安装位置,默认为 “C:\msys64”
修改包管理器环境配置:包管理器仅使用 /etc/pacman.conf
里配置的环境
# 一定要保留 msys,它是所有环境的基础
# 其余按需启用,我们只需要用到 ucrt64
[msys]
Include = /etc/pacman.d/mirrorlist.msys
[ucrt64]
Include = /etc/pacman.d/mirrorlist.ucrt64
# [clangarm64]
# Include = /etc/pacman.d/mirrorlist.mingw
# [mingw32]
# Include = /etc/pacman.d/mirrorlist.mingw
# [mingw64]
# Include = /etc/pacman.d/mirrorlist.mingw
# [clang32]
# Include = /etc/pacman.d/mirrorlist.mingw
# [clang64]
# Include = /etc/pacman.d/mirrorlist.mingw
修改软件源:上面我们启用了两个环境,对应两个软件源列表:
# 在列表中找到中科大源(清华源、阿里云源等也可)并将其移到最上面
# /etc/pacman.d/mirrorlist.msys
## Primary
Server = https://mirrors.ustc.edu.cn/msys2/msys/$arch/
# /etc/pacman.d/mirrorlist.ucrt64
## Primary
Server = https://mirrors.ustc.edu.cn/msys2/mingw/ucrt64/
2. 包管理器
MSYS2 使用 pacman 这个包管理器来管理软件,它的基本使用如下:
pacman -Syu # 同步和更新系统中的所有软件包
pacman -Ss <name or part of the name of the package> # 查找软件包
pacman -S <name of the package> # 安装软件包
pacman -R <name of the package> # 删除软件包
pacman -Rns $(pacman -Qdtq) # 清理未使用的依赖和配置文件
pacman -Scc # 清理缓存
ucrt64 环境下的软件包包名以 mingw-w64-ucrt-x86_64-
作为前缀,看起来很长,可以使用 pacboy
来简化包名输入:
pacman -S pactoys # 安装 pacboy
pacboy help # 查看帮助,其中提到可以用 name:u 来替代上面的 ucrt64 长包名
# ...
# name:u - translates to mingw-w64-ucrt-x86_64-name
# ...
3. 安装 C & C++ 工具链
# toolchain 包含 gcc、gdb、make、C 标准库等等
pacman -Syu
pacboy -S toolchain:u
pacman -Scc
3. Visual Studio Code 的安装与简单配置
安装
通过 System Installer-x86_64 下载,下载完成后根据提示安装,完成后再安装 C/C++
插件即可
配置 vscode terminal(将 MSYS2 bash 设置为默认终端):
-
Ctrl + ,
打开设置 -
搜索
@feature:terminal Profiles
,找到下图项,点击Edit in settings.json
-
设置为:
{ "terminal.integrated.profiles.windows": { "bash (MSYS2)": { "path": "C:\\msys64\\usr\\bin\\bash.exe", "args": [ "--login", "-i" ], "env": { "CHERE_INVOKING": "1" } } }, "terminal.integrated.defaultProfile.windows": "bash (MSYS2)" }
配置 IntelliSense(自动补全、错误提示等等):
- 建立一个 C 项目并在 vscode 中打开
Ctrl + Shift + P
打开命令面板- 搜索
C/C++: Edit Configurations (UI)
并点击进入,会在项目根目录下生成.vsocde/c_cpp_properties.json
文件,一般不用修改(依情况而定)
为 mingw32-make 创建别名:
MSYS2 环境下 make 为 mingw32-make,为了贴近习惯可以建立别名。Ctrl + Shift + Tilda
(Tilda 是 Esc 正下方的第一个键)打开 vscode terminal,执行下面的指令:
echo "alias make='mingw32-make'" >> ~/.bashrc && source ~/.bashrc
4. 在 Visual Studio Code 中编译、运行和调试 C & C++ 程序
可以通过 make 配置自动化编译、运行和调试 C & C++ 程序,所有的配置项需要放在一个 Makefile 文件中,下面提供一个简单的 Makefile 模板:
# 源文件目录
SRC_DIRS = CLearning01 \
CLearning02 \
Clearing03
vpath %.c $(SRC_DIRS)
# 编译器及其选项设置
CC = gcc
CFLAGS = -g -std=c2x
INCLUDES = $(addprefix -I, $(SRC_DIRS))
LIBS = -lm -lncursesw
# 源程序、可执行文件、中间程序、依赖文件
SRC = main.c
EXE = main.exe
OBJ = $(SRC:.c=.o)
DEPS = $(SRC:.c=.d)
-include $(DEPS)
# 编译
$(EXE): $(OBJ)
$(CC) $(OBJ) $(CFLAGS) $(INCLUDES) $(LIBS) -o $(EXE)
$(OBJ):
$(CC) $(CFLAGS) $(INCLUDES) -MMD -MP -c $(SRC) -o $(OBJ)
# 运行
run:
./$(EXE)
# 调试
debug:
gdb -tui $(EXE)
# 清理
clean:
rm -f $(EXE) $(OBJ) $(DEPS)
项目详情:
project/
├── CLearning01/
│ └── hello.c
├── CLearning02/
│ └── caculator.c
├── Clearing03/
│ └── print.c
├── main.c # 项目主源文件
├── Makefile # Makefile 文件
├── main.o # main.c 编译后生成的目标文件
└── main.d # 由 -MMD 选项生成的依赖文件
/* main.c */
// 一次只能 include 一个文件,想要运行哪个就 include 哪个,注意不要重名
#include "hello.c"
// #include "caculator.c"
// #include "print.c"
编译、运行、调试
# 清理旧文件,make 新程序前记得执行
make clean
# 编译
make
# 运行
make run
# 调试,使用 gdb 的 tui 模式,仍需要记住一些基础指令
# b n # 在第 n 行打上断点
# run # 运行至断点位置
# print a # 打印变量 a 的值
# until # 跳出循环
# step # 进入函数
# finish # 执行完当前函数
# q # 退出调试
make debug