构建系统
Zig 除了是一门编程语言外,本身还是一套完整的工具链,例如:
zig cc
、zig c++
C/C++ 编译器zig build
适用于 Zig/C/C++ 的构建系统
本章节就来介绍 Zig 的构建系统。
理念
Zig 使用 build.zig
文件来描述一个项目的构建步骤。
如其名字所示,该文件本身就是一个 Zig 程序,而不是类似 Cargo.toml
或 CMakeLists.txt
这样的领域特定语言(DSL)。
这样的好处很明显,表达能力更强,开发者只需要使用同一门语言即可进行项目构建,减轻了用户心智。
一个典型的构建文件如下:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
}
build
是构建的入口函数,而不是常见的 main
,真正的 main
函数定义在 build_runner.zig
中,这是由于 Zig 的构建分为两个阶段:
- 生成由
std.Build.Step
构成有向无环图(DAG) - 执行真正的构建逻辑
TIP
第一次接触 Zig 的构建流程,可能会觉得复杂,尤其是构建 Step 的依赖关系,但这是为了后续并发编译作基础。
如果没有 build_runner.zig
,让开发者自己去处理并发编译,将会是件繁琐且容易出错的事情。
Step
会在下一小节中会重点讲述,这里介绍一下上面这个构建文件的其他部分:
b.standardTargetOptions
: 允许构建器读取来自命令行参数的构建目标三元组。b.standardOptimizeOption
: 允许构建器读取来自命令行参数的构建优化模式。b.addExecutable
:创建一个Build.Step.Compile
并返回对应的指针,其参数为std.Build.ExecutableOptions
。b.path
:该函数用于指定获取当前项目的源文件路径,请勿手动为root_source_file
赋值!
🅿️ 提示
标准构建会产生两个目录,一个是 zig-cache
、一个是 zig-out
,第一个是缓存目录(这有助于加快下次构建),第二个是安装目录,不是由项目决定,而是由用户决定(通过 zig build --prefix
参数),默认为 zig-out
。
Step
Step 可以称为构建时的步骤,它们将构成一个有向无环图。可以通过 Step 来指定构建过程之间的依赖管理,例如要构建的二进制程序 A 依赖一个库 B,那么我们可以在构建 A 前先构建出 B,而 B 的构建依赖于 另一个程序生成的数据 C,此时我们可以再指定构建库 B 前先构建出数据 C,大致的图如下:
数据C
|
C --> B --> A
| |
| 程序A
|
库B
例如我们可以在 build.zig
中添加一个运行程序的步骤:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// zig 提供了一个方便的函数允许我们直接运行构建结果
const run_exe = b.addRunArtifact(exe);
// 注意:该步骤可选,显式声明运行依赖于构建
// 这会使运行是从构建输出目录(默认为 zig-out/bin )运行而不是构建缓存中运行
// 不过,如果应用程序运行依赖于其他已存在的文件(例如某些 ini 配置文件)
// 这可以确保它们正确的运行
run_exe.step.dependOn(b.getInstallStep());
// 注意:此步骤可选
// 此操作允许用户通过构建系统的命令传递参数,例如 zig build -- arg1 arg2
// 当前是将参数传递给运行构建结果
if (b.args) |args| {
run_exe.addArgs(args);
}
// 指定一个 step 为 run
const run_step = b.step("run", "Run the application");
// 指定该 step 依赖于 run_exe,即实际的运行
run_step.dependOn(&run_exe.step);
}
以上代码中,我们可以使用 zig build run -- arg1
向构建产物传递参数!
🅿️ 提示
值得注意的是,b.installArtifact
是将构建放入 install
这一默认的 step 中。
如果我们想要重新创建一个全新的 install,可以使用 b.addInstallArtifact
。
它会返回一个新的 InstallArtifact
,让对应的 step 依赖它即可!
基本使用
构建模式
zig 提供了四种构建模式(Build Mode):
- Debug
- ReleaseFast
- ReleaseSafe
- ReleaseSmall
如果在 build.zig
中使用了 standardOptimizeOption
,则构建系统会接收命令行的参数来决定实际构建模式(缺省时为 Debug),参数类型为 -Doptimize
,例如 zig build -Doptimize=Debug
就是以 Debug 模式构建。
以下讲述四种构建模式的区别:
Debug | ReleaseFast | ReleaseSafe | ReleaseSmall |
---|---|---|---|
构建速度很快 | 构建速度慢 | 构建速度慢 | 构建速度慢 |
启用安全检查 | 启用安全检查 | 启用安全检查 | 禁用安全检查 |
较差的运行效率 | 很好的运行效率 | 中等的运行效率 | 中等的运行效率 |
二进制体积大 | 二进制体积大 | 二进制体积大 | 二进制体积小 |
无复现构建 | 可复现构建 | 可复现构建 | 可复现构建 |
关于 Debug 不可复现的原因
关于为什么 Debug 是不可复现的,zig 官方手册并未给出具体说明,以下内容为询问社区获得:
在 Debug 构建模式下,编译器会添加一些随机因素进入到程序中(例如内存结构不同),所以任何没有明确说明内存布局的容器在 Debug 构建下可能会有所不同,这便于我们在 Debug 模式下快速暴露某些错误。
有意思的是,这并不会影响程序正常运行,除非你的程序逻辑有问题。
这是 zig 加强安全性的一种方式(尽可能提高安全性但又不至于造成类似 Rust 开发时过重的心智负担)。
CLI 参数
通过 b.option
使构建脚本部分配置由用户决定(通过命令行参数传递),这也可用于依赖于当前包的其他包。
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 使用 option 来获取命令参数决定是否剥离调试信息
const is_strip =
b.option(bool, "is_strip", "whether strip executable") orelse
false;
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
// 设置 exe 的 strip
.strip = is_strip,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
}
以上,我们通过使用 b.option
来实现从命令行读取一个参数决定是否剥离二进制程序的调试信息,使用 zig build --help
可以看到输出多了一行:
Project-Specific Options:
-Dis_strip=[bool] whether strip executable
Options 编译期配置
Options 允许我们将一些信息传递到项目中,例如我们可以以此实现让程序打印构建时的时间戳:
const std = @import("std");
// timestamp 这个包是通过 build.zig 添加的
const timestamp = @import("timestamp");
pub fn main() !void {
std.debug.print("build time stamp is {}\n", .{timestamp.time_stamp});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 通过标准库获取时间戳
const timestamp = std.time.timestamp();
// 创建一个 options
const options = b.addOptions();
// 向 options 添加 option, 变量名是time_stamp
options.addOption(i64, "time_stamp", timestamp);
// 向 exe 中添加 options
exe.root_module.addOptions("timestamp", options);
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
}
可以发现,我们使用 b.addOptions
创建了一个 options,并且向其中添加了 option,最后将整个 options 塞入二进制程序的构建中,这会允许我们通过 @import
来将 options 作为包导入。
🅿️ 提示
事实上,在 build.zig
中的 options,会在编译时转为一个规范的 zig 包传递给程序,这就是我们为何能够像普通包一样 import
它们的原因。
构建静/动态链接库
通常我们定义一个 lib
的方式如下:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 使用默认提供的构建目标,支持我们从命令行构建时指定构建目标(架构、系统、abi等等)
const target = b.standardTargetOptions(.{});
// 使用默认提供的优化方案,支持我们从命令行构建时指定构建模式
const optimize = b.standardOptimizeOption(.{});
// 尝试添加一个静态库
const lib = b.addStaticLibrary(.{
// 库的名字
.name = "example",
// 源文件地址
.root_source_file = b.path("src/root.zig"),
// 构建目标
.target = target,
// 构建模式
.optimize = optimize,
});
// 这代替原本的 lib.install,在构建时自动构建 lib
// 但其实这是不必要的,因为如果有可执行二进制程序构建使用了 lib,那么它会自动被构建
b.installArtifact(lib);
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 链接 lib
exe.linkLibrary(lib);
// 添加到顶级 install step 中作为依赖,构建 exe
b.installArtifact(exe);
}
对应地,如果要构建动态库可以使用 b.addSharedLibrary
。
通常,二进制可执行程序的构建结果会输出在 zig-out/bin
下,而链接库的构建结果会输出在 zig-out/lib
下。
如果要连接到系统的库,则使用 exe.linkSystemLibrary
,Zig 内部借助 pkg-config 实现该功能。示例:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 使用默认提供的构建目标,支持我们从命令行构建时指定构建目标(架构、系统、abi等等)
const target = b.standardTargetOptions(.{});
// 使用默认提供的优化方案,支持我们从命令行构建时指定构建模式
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "zip",
.root_source_file = b.path("src/main.zig"),
// 构建目标
.target = target,
// 构建模式
.optimize = optimize,
});
if (target.result.os.tag == .windows)
// 连接到系统的 ole32
exe.linkSystemLibrary("ole32")
else
// 链接到系统的 libz
exe.linkSystemLibrary("z");
// 链接到 libc
exe.linkLibC();
b.installArtifact(exe);
}
这会链接一个名为 libz 的库,约定库的名字不包含“lib”。
生成文档
zig 本身提供了一个实验性的文档生成器,它支持搜索查询,操作如下:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 构建一个 object,用于生成文档
const object = b.addObject(.{
.name = "object",
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
// 创建一个 step
const docs_step = b.step("docs", "Generate docs");
// 生成文档
const docs_install = b.addInstallDirectory(.{
// 指定文档来源
.source_dir = object.getEmittedDocs(),
// 指定安装目录
.install_dir = .prefix,
// 指定文档子文件夹
.install_subdir = "docs",
});
docs_step.dependOn(&docs_install.step);
}
以上代码定义了一个名为 docs
的 Step,并将 addInstallDirectory
操作作为依赖添加到 docs
Step 上。
单元测试
每个文件可以使用 zig test
命令来执行测试,但实际开发中这样很不方便,zig 的构建系统提供了另外一种方式来处理当项目变得复杂时的测试。
使用构建系统执行单元测试时,构建器和测试器会通过 stdin 和 stdout 进行通信,以便同时运行多个测试,并且可以有效地报告错误(不会将错误混到一起),但这导致了无法 在单元测试中写入 stdin,这会扰乱测试器的正常工作。另外, zig 将引入一个额外的机制,允许 预测 panic
。
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// 此处开始构建单元测试
// 构建一个单元测试的 Compile
const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 执行单元测试
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// 如果想要跳过外部来自于其他包的单元测试(例如依赖中的包)
// 可以使用 skip_foreign_checks
run_exe_unit_tests.skip_foreign_checks = true;
// 构建一个 step,用于执行测试
const test_step = b.step("test", "Run unit tests");
// 测试 step 依赖上方构建的 run_exe_unit_tests
test_step.dependOn(&run_exe_unit_tests.step);
}
以上代码中,先通过 b.addTest
构建一个单元测试的 Compile
,随后进行执行并将其绑定到 test
Step 上。
高级功能
交叉编译
得益于 LLVM 的存在,zig 支持交叉编译到任何 LLVM 的目标代码,zig 可以很方便的处理交叉编译,只需要指定好恰当的 target 即可。
关于所有的 target,可以使用 zig targets
查看。
最常用的一个 target 设置可能是 b.standardTargetOptions
,它会允许读取命令行输入来决定构建目标 target,它返回一个 ResolvedTarget
。
如果需要手动指定一个 target,可以手动构建一个 std.Target.Query
传递给构建(addExecutable
和 addStaticLibrary
等),如:
// 构建一个target
const target_query = std.Target.Query{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
};
const ResolvedTarget = std.Build.ResolvedTarget;
// 解析的target
const resolved_target: ResolvedTarget = b.resolveTargetQuery(target_query);
// 解析结果
const target: std.Target = resolved_target.result;
_ = target;
// 构建 exe
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("main.zig"),
// 实际使用的是resolved_target
.target = resolved_target,
.optimize = optimize,
});
值得注意的是,目前 zig 已经将 target query
和 resolved target
完全分开,如果要手动指定构建目标,需要先创建一个 Query
,再使用 b.resolveTargetQuery
进行解析。
关于该部分的变动可以参考此处的 PR:Move many settings from being per-Compilation to being per-Module.
embedFile
@embedFile
是由 zig 提供的一个内嵌文件的方式,它的引入规则与 @import
相同。
在 build.zig
直接使用 addAnonymousImport
添加一个匿名模块即可,如:
const std = @import("std");
const hello = @embedFile("hello");
// const hello = @embedFile("hello.txt"); 均可以
pub fn main() !void {
std.debug.print("{s}\n", .{hello});
}
Hello, World!
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addAnonymousImport(
"hello",
.{ .root_source_file = b.path("src/hello.txt") },
);
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// zig 提供了一个方便的函数允许我们直接运行构建结果
const run_cmd = b.addRunArtifact(exe);
// 指定依赖
run_cmd.step.dependOn(b.getInstallStep());
// 传递参数
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 指定一个 step 为 run
const run_step = b.step("run", "Run the app");
// 指定该 step 依赖于 run_exe,即实际的运行
run_step.dependOn(&run_cmd.step);
}
不仅仅是以上两种方式,匿名模块还支持直接使用其他程序输出,具体可参考下面一小节。
执行外部命令
zig 的构建系统还允许我们执行一些额外的命令,录入根据 json 生成某些特定的文件(例如 zig 源代码),构建其他的编程语言(不只是 C / C++),如Golang、Rust、前端项目构建等等!
例如我们可以让 zig 在构建时调用系统的 sh 来输出 hello 并使用 @embedFile
传递给包:
const std = @import("std");
const hello = @embedFile("hello");
pub fn main() !void {
std.debug.print("{s}", .{hello});
}
const std = @import("std");
pub fn build(b: *std.Build) !void {
// 标准构建目标
const target = b.standardTargetOptions(.{});
// 标准构建模式
const optimize = b.standardOptimizeOption(.{});
// 在 windows 平台无法使用 bash,故我们直接返回
if (target.result.os.tag == .windows) {
return;
}
// 添加一个二进制可执行程序构建
const exe = b.addExecutable(.{
.name = "zig",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 构建一个运行命令
const run_sys_cmd = b.addSystemCommand(&.{
"/bin/sh",
"-c",
});
// 添加参数,此方法允许添加多个参数
// 也可以使用 addArg 来添加单个参数
run_sys_cmd.addArgs(&.{
"echo hello",
});
// 尝试运行命令并捕获标准输出
// 也可以使用 captureStdErr 来捕获标准错误输出
const output = run_sys_cmd.captureStdOut();
// 添加一个匿名的依赖
exe.root_module.addAnonymousImport("hello", .{ .root_source_file = output });
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// zig 提供了一个方便的函数允许我们直接运行构建结果
const run_cmd = b.addRunArtifact(exe);
// 指定依赖
run_cmd.step.dependOn(b.getInstallStep());
// 传递参数
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 指定一个 step 为 run
const run_step = b.step("run", "Run the app");
// 指定该 step 依赖于 run_exe,即实际的运行
run_step.dependOn(&run_cmd.step);
}
构建纯 C++ 项目
由于 GTK 的 C++ 构建过于复杂(需要手动编译gtkmm),故我们这里选择构建一个 tinytetris:
WARNING
注意:由于依赖了 curses 库,故只能在 linux 进行编译!
const std = @import("std");
pub fn build(b: *std.Build) void {
// 构建目标
const target = b.standardTargetOptions(.{});
// 构建优化模式
const optimize = b.standardOptimizeOption(.{});
if (target.result.os.tag == .windows) {
return;
}
// 添加一个二进制可执行程序构建
// 注意:我们在这里并没有使用 root_source_file 字段
// 该字段是为 zig 源文件准备的
const exe = b.addExecutable(.{
.name = "zig",
.target = target,
.optimize = optimize,
});
// 添加 C 源代码文件,两个参数:
// 源代码路径(相对于build.zig)
// 传递的 flags
// 多个 C 源代码文件可以使用 addCSourceFiles
exe.addCSourceFile(.{
.file = b.path("src/main.cc"),
.flags = &.{},
});
// 同理对于 C 标准库可以使用 linkLibC
// 链接C++ 标准库
exe.linkLibCpp();
// 链接系统库 ncurses
exe.linkSystemLibrary("ncurses");
// 添加到顶级 install step 中作为依赖
b.installArtifact(exe);
// 创建一个运行
const run_cmd = b.addRunArtifact(exe);
// 依赖于构建
run_cmd.step.dependOn(b.getInstallStep());
// 运行时参数传递
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 运行的 step
const run_step = b.step("run", "Run the app");
// 依赖于前面的运行
run_step.dependOn(&run_cmd.step);
}
#include <ctime>
#include <curses.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
// block layout is: {w-1,h-1}{x0,y0}{x1,y1}{x2,y2}{x3,y3} (two bits each)
int x = 431424, y = 598356, r = 427089, px = 247872, py = 799248, pr,
c = 348480, p = 615696, tick, board[20][10],
block[7][4] = {{x, y, x, y},
{r, p, r, p},
{c, c, c, c},
{599636, 431376, 598336, 432192},
{411985, 610832, 415808, 595540},
{px, py, px, py},
{614928, 399424, 615744, 428369}},
score = 0;
// extract a 2-bit number from a block entry
int NUM(int x, int y) { return 3 & block[p][x] >> y; }
// create a new piece, don't remove old one (it has landed and should stick)
void new_piece() {
y = py = 0;
p = rand() % 7;
r = pr = rand() % 4;
x = px = rand() % (10 - NUM(r, 16));
}
// draw the board and score
void frame() {
for (int i = 0; i < 20; i++) {
move(1 + i, 1); // otherwise the box won't draw
for (int j = 0; j < 10; j++) {
board[i][j] && attron(262176 | board[i][j] << 8);
printw(" ");
attroff(262176 | board[i][j] << 8);
}
}
move(21, 1);
printw("Score: %d", score);
refresh();
}
// set the value of the board for a particular (x,y,r) piece
void set_piece(int x, int y, int r, int v) {
for (int i = 0; i < 8; i += 2) {
board[NUM(r, i * 2) + y][NUM(r, (i * 2) + 2) + x] = v;
}
}
// move a piece from old (p*) coords to new
void update_piece() {
set_piece(px, py, pr, 0);
set_piece(px = x, py = y, pr = r, p + 1);
}
// remove line(s) from the board if they're full
void remove_line() {
for (int row = y; row <= y + NUM(r, 18); row++) {
c = 1;
for (int i = 0; i < 10; i++) {
c *= board[row][i];
}
if (!c) {
continue;
}
for (int i = row - 1; i > 0; i--) {
memcpy(&board[i + 1][0], &board[i][0], 40);
}
memset(&board[0][0], 0, 10);
score++;
}
}
// check if placing p at (x,y,r) will be a collision
int check_hit(int x, int y, int r) {
if (y + NUM(r, 18) > 19) {
return 1;
}
set_piece(px, py, pr, 0);
c = 0;
for (int i = 0; i < 8; i += 2) {
board[y + NUM(r, i * 2)][x + NUM(r, (i * 2) + 2)] && c++;
}
set_piece(px, py, pr, p + 1);
return c;
}
// slowly tick the piece y position down so the piece falls
int do_tick() {
if (++tick > 30) {
tick = 0;
if (check_hit(x, y + 1, r)) {
if (!y) {
return 0;
}
remove_line();
new_piece();
} else {
y++;
update_piece();
}
}
return 1;
}
// main game loop with wasd input checking
void runloop() {
while (do_tick()) {
usleep(10000);
if ((c = getch()) == 'a' && x > 0 && !check_hit(x - 1, y, r)) {
x--;
}
if (c == 'd' && x + NUM(r, 16) < 9 && !check_hit(x + 1, y, r)) {
x++;
}
if (c == 's') {
while (!check_hit(x, y + 1, r)) {
y++;
update_piece();
}
remove_line();
new_piece();
}
if (c == 'w') {
++r %= 4;
while (x + NUM(r, 16) > 9) {
x--;
}
if (check_hit(x, y, r)) {
x = px;
r = pr;
}
}
if (c == 'q') {
return;
}
update_piece();
frame();
}
}
// init curses and start runloop
int main() {
srand(time(0));
initscr();
start_color();
// colours indexed by their position in the block
for (int i = 1; i < 8; i++) {
init_pair(i, i, 0);
}
new_piece();
resizeterm(22, 22);
noecho();
timeout(0);
curs_set(0);
box(stdscr, 0, 0);
runloop();
endwin();
}
🅿️ 提示
关于头文件的引入,可以使用 addIncludePath
针对多个 C 源代码文件,zig 提供了函数 addCSourceFiles
用于便捷地添加多个源文件。
关于 libc++
的问题
zig 的工具链使用的是 libc++
(LLVM ABI),而GNU的则是 libstdc++
,两者的标准库实现略有不同,这会导致混用可能出现问题!
正确的做法是,手动编译依赖的源代码(一般是出现问题的),或者使用 -nostdinc++ -nostdlib++
指示不使用默认标准库,并链接 GNU 的标准库,具体可以参考该 issue。