gn学习存档
以下答案均由deepseek提供,仅作学习存档。
1. 举例说明action和action_foreach区别
场景设定
假设需要处理一组文件:
- 输入文件:
src/data/file1.txt,src/data/file2.txt,src/data/file3.txt - 处理逻辑:将每个
.txt文件转换为.processed文件 - 输出目录:
$target_gen_dir/processed/
1. 使用 action 的实现
特点:
- 需要 手动遍历文件 或 编写循环逻辑
- 适合处理 单个输入文件 或 需要批量处理但逻辑不可拆分 的场景
GN脚本:
# 手动列出所有输入输出对
inputs = ["src/data/file1.txt","src/data/file2.txt","src/data/file3.txt",
]outputs = ["$target_gen_dir/processed/file1.processed","$target_gen_dir/processed/file2.processed","$target_gen_dir/processed/file3.processed",
]action("process_files") {script = "scripts/process.py"inputs = inputs # 所有输入文件outputs = outputs # 所有输出文件args = ["--inputs", rebase_path(inputs, root_build_dir),"--outputs", rebase_path(outputs, root_build_dir),]
}
Python脚本 (process.py):
# 需要自行解析多个输入输出对
for input_file, output_file in zip(args.inputs, args.outputs):process_single_file(input_file, output_file)
缺点:
- 每新增一个文件,需手动更新
inputs和outputs列表 - 脚本需处理多文件逻辑,不够简洁
2. 使用 action_foreach 的实现
特点:
- 自动遍历每个输入文件,为每个文件单独执行命令
- 使用 占位符(如
{{source}})动态生成输出路径 - 适合 每个输入文件独立处理 的场景
GN脚本:
action_foreach("process_files") {script = "scripts/process_single.py" # 处理单个文件的脚本inputs = ["src/data/file1.txt","src/data/file2.txt","src/data/file3.txt",]outputs = [ "$target_gen_dir/processed/{{source_name_part}}.processed" ]args = ["--input", "{{source}}", # 自动替换为当前处理的输入文件路径"--output", "{{output}}", # 自动替换为对应的输出文件路径]
}
Python脚本 (process_single.py):
# 只需处理单个文件
process_single_file(args.input, args.output)
优势:
- 新增文件时只需更新
inputs列表,无需修改输出路径规则 - 脚本逻辑简单,每个实例仅处理一个文件
- 构建系统自动并行处理多个文件
3. 关键区别总结
| 特性 | action | action_foreach |
|---|---|---|
| 执行次数 | 整个目标执行一次脚本 | 每个输入文件执行一次脚本 |
| 输入输出关系 | 多对多(需手动管理) | 一对一(自动匹配) |
| 脚本复杂度 | 需处理多文件逻辑 | 仅处理单文件逻辑 |
| 路径占位符 | 不支持 | 支持 {{source}}, {{output}} |
| 适用场景 | 批量处理、聚合操作 | 独立文件处理、转换 |
| 新增文件维护成本 | 高(需更新输入输出列表) | 低(仅更新输入列表) |
4. 更直观的类比
-
action类似for 循环:# 伪代码 for file in inputs:process(file, output[file]) -
action_foreach类似map 函数:# 伪代码 list(map(lambda file: process(file), inputs))
5. 高级示例:批量图片转换
需求:将 assets/*.png 转换为缩略图 build/thumbnails/*.jpg
使用 action_foreach:
action_foreach("generate_thumbnails") {script = "scripts/resize_image.py"inputs = [ "assets/*.png" ] # 使用通配符outputs = [ "$target_gen_dir/thumbnails/{{source_name_part}}.jpg" ]args = ["--input", "{{source}}","--output", "{{output}}","--size", "128x128"]
}
对比 action:
需手动收集所有 .png 文件并生成对应的输出路径列表,代码冗余度高。
总结
- 优先使用
action_foreach:当每个输入文件需要独立处理时,它能显著简化配置。 - 保留
action:用于需要聚合多个输入生成单个输出,或处理逻辑无法拆分的场景(如合并文件)。
通过合理选择两者,可以提升构建脚本的可维护性和执行效率。
2. 解释一下group的作用
在 GN(生成 Ninja)构建系统中,group 是一种用于 逻辑分组目标 的构建规则,它本身不生成任何实际的构建输出(如可执行文件或库),而是将多个构建目标组织在一起,简化依赖管理和构建流程。以下是 group 的详细说明:
1. group 的核心作用
- 聚合依赖:将多个构建目标(如
executable、static_library、action等)打包成一个逻辑单元,其他目标只需依赖该group即可间接依赖所有子目标。 - 简化配置:在大型项目中,避免重复列出多个目标,提升构建脚本的可维护性。
- 模块化组织:将相关功能的目标归类到同一组,便于分层管理。
2. 基础语法示例
group("my_group") {deps = [":lib_common", # 静态库":generate_data", # 自定义构建步骤(action)":tests", # 测试可执行文件]
}
3. 典型使用场景
场景 1:统一管理多个依赖
假设主程序需要依赖多个基础库和生成的数据文件:
# 传统方式:逐个列出依赖
executable("main_app") {deps = [":lib_utils",":lib_network",":generated_version_header",]
}# 使用 group 优化后
group("core_dependencies") {deps = [":lib_utils",":lib_network",":generated_version_header",]
}executable("main_app") {deps = [ ":core_dependencies" ] # 依赖一个组代替多个目标
}
优势:
- 新增或删除依赖时,只需修改
group,无需改动所有引用这些依赖的目标。 - 依赖关系更清晰,尤其是当多个目标共享同一组依赖时。
场景 2:分层组织构建目标
在模块化项目中,按功能划分组:
# 网络模块组
group("networking") {deps = [":lib_http",":lib_websocket",":lib_protobuf_codec",]
}# 数据存储模块组
group("storage") {deps = [":lib_sqlite",":lib_serialization",]
}# 主程序依赖所有模块
executable("main_app") {deps = [":networking",":storage",]
}
场景 3:控制构建粒度
通过 group 控制是否构建某类目标(如测试、示例程序):
group("all_tests") {deps = [":unit_tests",":integration_tests",]
}# 通过命令行选择构建测试
# gn gen out/Debug --args="enable_tests=true"
if (enable_tests) {group("build_all") {deps = [ ":main_app", ":all_tests" ]}
} else {group("build_all") {deps = [ ":main_app" ]}
}
4. group 的高级用法
嵌套组
group("third_party") {deps = [":zlib",":openssl",]
}group("app_dependencies") {deps = [":third_party", # 嵌套组":lib_core",]
}
条件化依赖
根据构建参数动态包含目标:
group("optional_components") {if (enable_gpu) {deps += [ ":lib_gpu" ]}if (enable_audio) {deps += [ ":lib_audio" ]}
}
5. group 的注意事项
- 无实际输出:
group本身不会生成任何文件(如.a、.exe),仅用于逻辑分组。 - 依赖传递性:依赖
group的目标会继承组内所有deps,但组内的目标之间不自动依赖彼此,需自行管理内部依赖。 - 性能影响:GN 在处理
group时几乎无额外开销,最终生成的 Ninja 规则会直接展开组内所有目标。
6. 对比其他机制
| 机制 | 用途 | 与 group 的区别 |
|---|---|---|
deps | 直接声明依赖的构建目标 | group 是 deps 的聚合容器 |
public_deps | 传递依赖到依赖当前目标的其他目标 | group 不改变依赖传递性,仅聚合目标 |
bundle_data | 聚合资源文件(如配置文件、图片) | group 用于聚合构建目标,而非资源文件 |
7. 实际项目示例
假设项目结构如下:
project/
├── src/
│ ├── core/ # 核心模块
│ ├── network/ # 网络模块
│ └── main.cc
├── tests/ # 测试代码
└── BUILD.gn
BUILD.gn 配置:
# 核心库
static_library("lib_core") {sources = [ "src/core/*.cc" ]
}# 网络库
static_library("lib_network") {sources = [ "src/network/*.cc" ]
}# 测试可执行文件
executable("run_tests") {sources = [ "tests/*.cc" ]deps = [ ":lib_core", ":lib_network" ]
}# 定义组
group("all_components") {deps = [":lib_core",":lib_network",":run_tests",]
}# 默认构建目标
group("default") {deps = [ ":all_components" ]
}
通过 gn build out/Debug :default 即可构建所有组件。
总结
group 是 GN 中用于 逻辑聚合构建目标 的轻量级工具,适合以下场景:
- 简化多目标依赖管理
- 模块化组织大型项目
- 条件化控制构建内容
合理使用 group 可以显著提升构建脚本的可读性和可维护性,尤其是在协作开发中。
3. 为什么deps对应的值前面要加冒号
在 GN(Generate Ninja)构建系统中,依赖项(deps)前的冒号(:)是 目标标签(Target Label) 的语法规则,用于明确标识构建目标(而非文件路径)。以下是详细解释:
1. GN 标签(Label)的基本格式
GN 使用 标签 唯一标识构建目标,完整格式为:
"//path/to/dir:target_name"
//:表示从项目根目录开始的路径。path/to/dir:目标所在的目录(相对于项目根目录)。::分隔符,左边是路径,右边是目标名。target_name:在BUILD.gn文件中定义的目标名称。
2. 冒号的作用
(1) 标识目标名称
冒号后的部分 表示目标在 BUILD.gn 文件中的名称。例如:
# 在 src/foo/BUILD.gn 中定义:
executable("my_app") { ... }
- 引用该目标的标签为:
"//src/foo:my_app"。
(2) 区分路径与目标名
若省略冒号,GN 会尝试将整个字符串解释为路径,而非目标名。例如:
":my_app"→ 正确:引用当前目录下的my_app目标。"my_app"→ 错误:GN 会将其视为文件路径,尝试寻找my_app.cc或my_app.h,而非构建目标。
3. 不同场景下的写法示例
(1) 引用同一目录下的目标
直接使用 :target_name:
# 在同一 BUILD.gn 文件中:
static_library("utils") { ... }executable("demo") {deps = [ ":utils" ] # 正确:冒号引用当前文件的 utils 目标
}
(2) 引用其他目录的目标
使用绝对路径 + 冒号:
deps = ["//src/net:http_lib", # 引用 src/net 目录下的 http_lib"//third_party/zlib:z", # 引用第三方库的 z 目标
]
(3) 错误写法对比
deps = [ "utils" ] # 错误:GN 会寻找 utils.cc 文件,而非 utils 目标
4. 为什么需要强制使用冒号?
GN 通过冒号语法实现以下目标:
- 明确性:直接区分文件路径和构建目标,避免歧义。
- 可读性:一眼看出依赖的是目标而非文件。
- 错误预防:若忘记冒号,GN 会报错
No known target for path: "utils",帮助快速定位问题。
5. 特殊情况
(1) 隐式当前目录
若目标位于当前目录,路径部分可省略:
# 在 src/foo/BUILD.gn 中:
deps = [ ":utils" ] # 等价于 "//src/foo:utils"
(2) 默认目标
如果目录的 BUILD.gn 文件定义了 group("all"),可以直接引用目录路径:
deps = [ "//src/foo" ] # 等价于 "//src/foo:all"
总结
| 场景 | 正确写法 | 错误写法 |
|---|---|---|
| 同一目录下的目标 | deps = [ ":target" ] | deps = [ "target" ] |
| 其他目录的目标 | deps = [ "//path:target" ] | deps = [ "//path/target" ] |
引用目录默认目标(如 all) | deps = [ "//path" ] | - |
冒号是 GN 标签语法的核心规则,强制使用可确保构建系统正确解析依赖关系。
4. 举例说明loadable_module用法
在 GN 构建系统中,loadable_module 用于定义 可动态加载的模块(如插件、动态库)。它与 shared_library 类似,但在某些平台(如 Windows)下有特殊处理。以下是详细用法和示例:
1. loadable_module 核心作用
- 生成动态库文件:
- Windows:
.dll - Linux:
.so - macOS:
.dylib
- Windows:
- 明确语义:标识该模块为可动态加载(而非普通共享库),可能在编译选项或符号导出上有特殊处理。
2. 基本语法示例
# 示例:构建一个插件模块
loadable_module("my_plugin") {sources = ["src/plugin/plugin_main.cc","src/plugin/plugin_api.cc",]deps = ["//base:base", # 依赖基础库]# Windows 下需明确导出符号if (is_win) {defines = [ "MY_PLUGIN_EXPORT=__declspec(dllexport)" ]}
}
3. 完整示例:插件系统
项目结构
project/
├── src/
│ ├── main.cc # 主程序
│ └── plugin/
│ ├── plugin.h # 插件接口
│ ├── plugin.cc # 插件实现
│ └── BUILD.gn # 插件构建配置
└── BUILD.gn # 主程序配置
插件模块配置 (src/plugin/BUILD.gn)
# 定义可加载模块
loadable_module("my_plugin") {sources = ["plugin.cc","plugin.h",]deps = [ "//base" ] # 假设项目有一个基础库# 跨平台符号导出处理if (is_win) {defines = [ "PLUGIN_EXPORT=__declspec(dllexport)" ]} else {defines = [ "PLUGIN_EXPORT=__attribute__((visibility(\"default\")))" ]}
}
主程序配置 (BUILD.gn)
executable("main_app") {sources = [ "src/main.cc" ]deps = [ "//base" ]if (is_win) {defines = ["PLUGIN_EXPORT=__declspec(dllimport)"]} else {defines = ["PLUGIN_EXPORT=__attribute__((visibility(\"default\")))"]}
}
4. 代码实现
插件接口 (src/plugin/plugin.h)
#ifndef PLUGIN_H_
#define PLUGIN_H_#ifdef __cplusplus
extern "C" {
#endif// 定义插件导出函数
typedef const char* (*GetPluginNameFunc)();// 导出初始化函数
PLUGIN_EXPORT const char* GetPluginName();#ifdef __cplusplus
}
#endif#endif // PLUGIN_H_
插件实现 (src/plugin/plugin.cc)
#include "plugin.h"PLUGIN_EXPORT const char* GetPluginName() {return "My Awesome Plugin";
}
主程序动态加载 (src/main.cc)
#include <iostream>
#include <string>
#include "plugin.h"// 动态加载库的头文件(平台相关)
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endifint main() {// 1. 加载插件
#ifdef _WIN32HMODULE plugin = LoadLibrary("my_plugin.dll");auto GetPluginName = (GetPluginNameFunc)GetProcAddress(plugin, "GetPluginName");
#elsevoid* plugin = dlopen("libmy_plugin.so", RTLD_LAZY);auto GetPluginName = (GetPluginNameFunc)dlsym(plugin, "GetPluginName");
#endif// 2. 使用插件if (GetPluginName) {std::cout << "Plugin Name: " << GetPluginName() << std::endl;}// 3. 卸载插件
#ifdef _WIN32FreeLibrary(plugin);
#elsedlclose(plugin);
#endifreturn 0;
}
5. 关键注意事项
(1) 符号导出
- Windows:必须用
__declspec(dllexport)导出符号。 - Linux/macOS:使用
__attribute__((visibility("default")))并配合编译选项-fvisibility=hidden。
(2) 输出路径
- 默认输出到
$root_build_dir,需确保主程序运行时能找到插件:# 在可执行目标中指定库路径 executable("main_app") {lib_dirs = [ "$root_out_dir" ] # 添加插件输出目录 }
(3) 与 shared_library 的区别
| 特性 | loadable_module | shared_library |
|---|---|---|
| 语义 | 明确为可动态加载的模块 | 普通共享库 |
| Windows 导入库 | 不生成 .lib 文件 | 生成 .lib 文件 |
| 适用场景 | 插件、运行时加载的功能模块 | 需要被其他库静态依赖的动态库 |
6. 高级用法:跨平台宏封装
在头文件中统一处理导出符号:
// plugin_export.h
#pragma once#if defined(_WIN32)#define PLUGIN_EXPORT __declspec(dllexport)
#else#define PLUGIN_EXPORT __attribute__((visibility("default")))
#endif
7. 构建命令
# 生成构建文件
gn gen out/Default# 编译
ninja -C out/Default main_app my_plugin# 运行(确保插件在库路径中)
./out/Default/main_app # Linux/macOS
out\Default\main_app.exe # Windows
总结
loadable_module 是 GN 中构建动态加载模块的首选规则,尤其适合插件系统开发。通过正确定义导出符号和依赖关系,结合平台特定的加载 API(如 dlopen/LoadLibrary),可以实现灵活的模块化架构。
5.为什么需要导出符号?
在编程中,导出符号(Exporting Symbols) 指的是将动态库(如 .dll、.so、.dylib)中的 函数、变量或类 暴露给外部程序或其他模块使用的过程。导出符号的目的是让其他代码能够通过动态加载(dlopen/LoadLibrary)或动态链接(隐式链接)的方式调用这些符号。
为什么需要导出符号?
动态库(或插件)中的代码默认是 内部可见的(仅库内可用),外部程序无法直接访问。通过 导出符号,开发者可以:
- 暴露接口:让其他程序或模块调用动态库中的特定功能。
- 隐藏实现细节:仅导出必要的接口,保持内部代码的封装性。
- 跨平台兼容:不同操作系统对动态库的符号可见性有不同规则,导出符号是统一处理这些差异的关键。
导出符号的典型场景
1. Windows 动态库(DLL)
- 导出符号:使用
__declspec(dllexport)修饰需要导出的函数或类。 - 导入符号:使用
__declspec(dllimport)修饰外部调用的函数或类。
示例:
// 导出函数
__declspec(dllexport) void MyFunction() {// 实现代码
}// 导出类
class __declspec(dllexport) MyClass {// 类定义
};
2. Linux/macOS 共享库(.so/.dylib)
- 使用编译选项
-fvisibility=hidden隐藏所有符号。 - 通过
__attribute__((visibility("default")))显式导出需要的符号。
示例:
// 导出函数
__attribute__((visibility("default"))) void MyFunction() {// 实现代码
}// 导出类
class __attribute__((visibility("default"))) MyClass {// 类定义
};
导出符号的实际作用
Windows 中的表现
- 导出符号会生成 导出表(Export Table),记录可被外部调用的函数/变量。
- 生成 导入库(.lib 文件),供其他程序隐式链接时使用。
Linux/macOS 中的表现
- 导出符号会在共享库的 符号表 中标记为
GLOBAL,外部程序可通过dlsym查找并调用。
如何在构建系统中处理导出符号?
1. GN 构建系统
在 loadable_module 或 shared_library 中通过 defines 定义导出宏:
# 定义动态库目标
loadable_module("my_plugin") {sources = [ "plugin.cc" ]# Windows 下导出符号if (is_win) {defines = [ "MY_API=__declspec(dllexport)" ]}# Linux/macOS 下导出符号if (is_linux || is_mac) {defines = [ "MY_API=__attribute__((visibility(\\"default\\")))" ]cflags = [ "-fvisibility=hidden" ] # 隐藏其他符号}
}
2. CMake 构建系统
使用 target_compile_definitions 和 visibility 属性:
add_library(my_plugin SHARED plugin.cc)
if (WIN32)target_compile_definitions(my_plugin PRIVATE MY_API=__declspec(dllexport))
else()target_compile_options(my_plugin PRIVATE -fvisibility=hidden)target_compile_definitions(my_plugin PRIVATE MY_API=__attribute__((visibility("default"))))
endif()
导出符号的常见问题
1. 未导出符号
- 现象:外部程序调用动态库函数时,报错
undefined symbol。 - 解决:检查是否遗漏了导出修饰符或编译选项。
2. 跨平台兼容性
- 统一处理:通过预处理器宏封装平台差异:
#if defined(_WIN32)#define MY_API __declspec(dllexport) #else#define MY_API __attribute__((visibility("default"))) #endifMY_API void MyFunction() { ... }
3. C++ 符号修饰(Name Mangling)
- 问题:C++ 编译器会修改函数名(如
MyFunction→_Z10MyFunctionv),导致动态加载时找不到符号。 - 解决:用
extern "C"包裹函数,禁止名称修饰:extern "C" {MY_API void MyFunction() { ... } }
总结
- 导出符号是将动态库接口暴露给外部的必要操作。
- Windows 用
__declspec(dllexport),Linux/macOS 用visibility属性和编译选项。 - 构建系统(如 GN、CMake)可通过宏定义简化跨平台导出。
正确导出符号是开发动态库和插件系统的核心技能!
6. 动态加载和动态链接的区别
动态加载和动态链接是两种不同的动态库使用方式,它们的核心区别在于 库的加载时机和控制方式。以下是详细对比:
1. 动态链接(Dynamic Linking)
定义
动态链接发生在 程序启动时,由操作系统自动加载依赖的动态库(如 .dll、.so),并将库中的符号(函数、变量)绑定到程序的内存空间中。
关键特点
- 加载时机:程序启动时自动加载。
- 使用方式:
在编译时声明依赖(如-lxxx),运行时由操作系统完成链接。 - 符号解析:所有依赖符号必须在启动时存在,否则程序无法运行。
- 生命周期:库的加载和卸载由操作系统管理。
- 示例:
// 隐式调用动态库中的函数(无需代码显式加载) #include <stdio.h> // 标准库通常动态链接 int main() {printf("Hello, dynamic linking!"); // 依赖 libc.so/libc.dllreturn 0; }
优点
- 开发简单:代码无需处理库的加载和卸载。
- 内存共享:同一库的代码可被多个进程共享,节省内存。
缺点
- 启动依赖:若库缺失或版本不兼容,程序直接崩溃。
- 灵活性差:无法按需加载或替换库。
2. 动态加载(Dynamic Loading)
定义
动态加载是 程序运行时 通过代码手动加载动态库,并主动解析符号地址,实现按需使用库的功能。
关键特点
- 加载时机:程序运行时按需加载。
- 使用方式:
通过平台API手动加载(如LoadLibrary/dlopen)、获取符号地址(如GetProcAddress/dlsym)、卸载库(如FreeLibrary/dlclose)。 - 符号解析:可处理符号缺失(通过返回
NULL),程序仍可运行。 - 生命周期:库的加载和卸载由开发者控制。
- 示例:
// 显式动态加载(以 Windows 为例) #include <windows.h> int main() {HINSTANCE hDll = LoadLibrary("my_plugin.dll");if (hDll) {typedef void (*PluginFunc)();PluginFunc func = (PluginFunc)GetProcAddress(hDll, "DoSomething");if (func) func();FreeLibrary(hDll);}return 0; }
优点
- 灵活控制:按需加载库,减少启动时间和内存占用。
- 容错性强:库缺失或符号错误可通过代码处理,避免程序崩溃。
- 热插拔支持:适合插件系统,运行时动态扩展功能。
缺点
- 代码复杂:需手动管理库的加载、符号解析和卸载。
- 无内存共享:同一库多次加载可能占用更多内存。
3. 核心区别对比
| 对比维度 | 动态链接 | 动态加载 |
|---|---|---|
| 加载时机 | 程序启动时自动加载 | 程序运行时手动加载 |
| 符号绑定 | 启动时完成,全部符号必须存在 | 运行时按需解析,允许部分符号缺失 |
| 依赖管理 | 由操作系统/链接器管理 | 由开发者代码控制 |
| 代码复杂度 | 低(无需显式代码) | 高(需手动加载、解析、卸载) |
| 适用场景 | 基础库(如libc)、通用依赖 | 插件系统、可选功能模块 |
| 内存占用 | 共享内存,节省资源 | 可能重复加载,占用更多内存 |
| 错误处理 | 启动失败(库缺失直接崩溃) | 可捕获错误并降级运行 |
4. 实际应用场景
动态链接的典型场景
- 使用系统标准库(如C/C++运行时库)。
- 依赖第三方通用库(如OpenSSL、SQLite)。
动态加载的典型场景
- 插件系统:如浏览器扩展、IDE插件。
- 功能模块化:按需加载不同功能模块(如游戏DLC)。
- 兼容性处理:运行时检测硬件或操作系统特性,加载适配库。
总结
- 动态链接:省心但死板,适合强依赖的基础库。
- 动态加载:灵活但复杂,适合需运行时控制的可选功能。
根据需求选择:
- 如果依赖是程序的核心且必须存在的 → 动态链接。
- 如果依赖是可选或需热插拔的 → 动态加载。
7. 举例说明shared_library用法
省流版:和loadable_module基本一致,只是在构建主程序的时候需要添加公共库产物作为依赖,并且主程序源文件中把动态加载和卸载的代码删掉。
在 GN 构建系统中,shared_library 用于构建 动态链接库(Windows 的 .dll、Linux 的 .so、macOS 的 .dylib),允许多个程序共享同一份代码。以下是详细的用法说明和跨平台示例:
1. 基础用法
目标定义
# 定义一个共享库
shared_library("mylib") {sources = ["src/mylib/mylib.cc","src/mylib/mylib.h",]defines = [ "MYLIB_EXPORTS" ] # 标识当前正在构建库deps = [ "//third_party:zlib" ] # 依赖其他库
}
关键参数
sources:库的源码和头文件。defines:定义宏,用于控制符号导出(如MYLIB_EXPORTS)。deps:依赖的其他目标(静态库、其他共享库等)。
2. 跨平台符号导出
头文件定义 (src/mylib/mylib.h)
#ifndef MYLIB_H_
#define MYLIB_H_// 导出函数
MYLIB_API int Add(int a, int b);#endif // MYLIB_H_
GN 构建配置
shared_library("mylib") {sources = [ "src/mylib/mylib.cc" ]defines = [ "MYLIB_EXPORTS" ] # 只在构建此库时定义 MYLIB_EXPORTSif (is_win) {defines += [ "MYLIB_API=__declspec(dllexport)" ]} else {defines += [ "MYLIB_API=__attribute__((visibility(\"default\")))" ]}
}
3. 其他目标依赖共享库
主程序使用共享库 (src/main/BUILD.gn)
executable("main_app") {sources = [ "src/main/main.cc" ]deps = [ ":mylib" ] # 依赖共享库if (is_win) {defines += [ "MYLIB_API=__declspec(dllimport)" ]} else {defines += [ "MYLIB_API=__attribute__((visibility(\"default\")))" ]}
}
主程序代码 (src/main/main.cc)
#include "mylib/mylib.h"
#include <iostream>int main() {std::cout << "1 + 2 = " << Add(1, 2) << std::endl;return 0;
}
4. 高级配置
版本号和 SONAME(Linux/macOS)
shared_library("mylib") {# 设置版本号(Linux/macOS 生成 libmylib.so.1.0.0)output_name = "mylib"output_extension = ".so"if (is_linux || is_mac) {ldflags = ["-Wl,-soname,libmylib.so.1", # SONAME"-Wl,-version-script=src/mylib/version.script",]}
}
版本脚本 (src/mylib/version.script)
MYLIB_1.0 {global:Add;local:*;
};
5. 完整项目结构示例
project/
├── src/
│ ├── main/
│ │ ├── main.cc
│ │ └── BUILD.gn
│ └── mylib/
│ ├── mylib.cc
│ ├── mylib.h
│ └── BUILD.gn
├── third_party/
│ └── BUILD.gn
└── BUILD.gn
根目录 BUILD.gn
group("default") {deps = ["//src/main:main_app","//src/mylib:mylib",]
}
6. 构建与验证
生成构建文件
gn gen out/Default
编译
ninja -C out/Default
运行主程序
# Linux/macOS
LD_LIBRARY_PATH=out/Default ./out/Default/main_app# Windows(确保 mylib.dll 在 PATH 或输出目录)
out\Default\main_app.exe
7. 常见问题
Windows 下找不到 DLL
- 解决方案:
- 将 DLL 放在主程序同级目录。
- 设置
lib_dirs并确保运行时路径包含 DLL。
符号未导出
- 检查:
- 头文件中的
MYLIB_API是否正确定义。 - GN 配置中的
defines是否包含MYLIB_EXPORTS。
- 头文件中的
版本冲突(Linux)
- 解决方案:
使用version-script和soname控制符号版本。
总结
通过 shared_library 可轻松构建跨平台动态库,核心步骤包括:
- 定义导出宏(
MYLIB_API)处理平台差异。 - GN 配置:用
defines标记当前构建目标。 - 依赖管理:通过
deps传递共享库依赖。 - 运行时配置:确保动态库路径正确。
该方案适用于需要共享代码的模块化开发场景(如核心算法库、网络模块等)。
8.举例说明source_set
在 GN 构建系统中,source_set 用于将一组源代码文件组织成一个逻辑单元,不会生成实际的库文件(如 .a 或 .so),而是直接将代码编译到依赖它的目标中。它的核心作用是 代码复用 和 模块化代码组织。以下是具体用法和示例:
1. source_set 核心特性
- 无输出文件:仅将代码合并到依赖目标中,不生成中间库文件。
- 编译优化:避免重复编译同一组代码(若多个目标依赖同一
source_set,代码会被多次编译)。 - 适用场景:
- 小型工具模块(如日志、配置解析)。
- 需要避免生成静态/动态库的场景。
- 需要不同编译选项的代码模块。
2. 基础示例
场景
项目有一个通用工具模块(common),包含日志和字符串处理工具,供主程序和测试程序共享。
目录结构
project/
├── src/
│ ├── common/
│ │ ├── log.cc
│ │ ├── log.h
│ │ ├── string_util.cc
│ │ ├── string_util.h
│ │ └── BUILD.gn # source_set 定义
│ ├── main/
│ │ ├── main.cc
│ │ └── BUILD.gn # 主程序
│ └── test/
│ ├── test.cc
│ └── BUILD.gn # 测试程序
└── BUILD.gn
source_set 定义 (src/common/BUILD.gn)
# 定义通用工具模块
source_set("common") {sources = ["log.cc","log.h","string_util.cc","string_util.h",]# 可选:定义公共宏或包含路径defines = [ "ENABLE_LOGGING=1" ]public_configs = [ ":common_config" ]
}# 公共配置(如头文件路径)
config("common_config") {include_dirs = [ "//src/common" ]
}
主程序依赖 (src/main/BUILD.gn)
executable("main_app") {sources = [ "main.cc" ]deps = ["//src/common:common", # 依赖 source_set]
}
测试程序依赖 (src/test/BUILD.gn)
executable("test") {sources = [ "test.cc" ]deps = ["//src/common:common",]
}
3. 对比 source_set 与 static_library
| 特性 | source_set | static_library |
|---|---|---|
| 输出文件 | 无 | 生成 .a 或 .lib |
| 编译次数 | 每个依赖目标独立编译代码 | 仅编译一次,链接到不同目标 |
| 适用场景 | 小型模块、需不同编译选项的代码 | 大型模块、需减少编译时间的场景 |
| 依赖传递 | 代码直接编译到目标中 | 通过链接静态库合并代码 |
| 编译速度 | 可能较慢(重复编译) | 较快(代码仅编译一次) |
4. 高级用法
(1) 条件化编译代码
通过 if 语句控制是否包含特定文件:
source_set("common") {sources = ["log.cc","log.h",]if (enable_advanced_features) {sources += [ "advanced_util.cc" ]}
}
(2) 合并多个 source_set
将多个 source_set 组合成一个更大的模块:
source_set("all_utils") {sources = []deps = [":common",":network_utils",]
}
(3) 不同目标的编译选项
允许不同目标为同一 source_set 指定不同编译选项:
executable("main_app") {deps = [ ":common" ]# 覆盖 common 中的 definesdefines = [ "ENABLE_LOGGING=0" ]
}
5. 常见问题
Q1:source_set 和 group 有什么区别?
source_set:包含代码文件,参与编译。group:仅逻辑分组,不包含代码。
Q2:何时应避免使用 source_set?
- 代码规模较大时(重复编译影响性能)。
- 需要跨目标共享同一编译结果时。
Q3:如何减少重复编译?
使用 static_library 替代 source_set,确保代码仅编译一次。
总结
通过 source_set,您可以:
- 模块化组织小型代码块(如工具类)。
- 灵活控制编译选项(不同目标可覆盖配置)。
- 避免生成多余的库文件。
典型应用场景包括:
- 跨平台项目中的平台适配代码。
- 测试工具代码。
- 需要条件化包含的代码模块。
9. 举例说明static_library
在 GN 构建系统中,static_library 用于生成 静态链接库(Windows 的 .lib、Linux/macOS 的 .a),其代码会在编译时完整嵌入到最终的可执行文件或动态库中。以下是详细用法和跨平台示例:
1. 基础用法
定义静态库
# src/math/BUILD.gn
# 定义一个数学工具静态库
static_library("math_utils") {sources = ["add.cc","add.h"]include_dirs = ["//src/math"] # 决定编译器会在哪些目录里查找头文件if (is_win) {defines = ["MATH_API=__stdcall"]} else {defines = ["MATH_API="]}
}
// src/math/add.cc
MATH_API int Add(int a, int b) {return a+b;
}
// src/math/add.h
MATH_API int Add(int a, int b);
主程序依赖静态库
# src/BUILD.gn
executable("calculator") {sources = ["calculator.cc"]deps=["//math:math_utils"]if (is_win) {defines = ["MATH_API=__stdcall"]} else {defines = ["MATH_API="]}
}
// src/calculator.cc
#include <iostream>
#include "math/add.h" // 对应的是路径名,和编译目标无关
using namespace std;int main() {cout<< Add(1,2) << endl;while(1) {}return 0;
}
2. 依赖其他静态库
分层依赖示例
# 底层算法库
static_library("algorithms") {sources = [ "src/algorithms/sort.cc" ]
}# 数学工具库依赖算法库
static_library("math_utils") {deps = [ ":algorithms" ]
}# 主程序依赖数学库(间接依赖算法库)
executable("calculator") {deps = [ ":math_utils" ]
}
3. 高级配置
优化编译选项
static_library("math_utils") {# 仅在该库中启用高级优化cflags = [ "-O3" ] # GCC/Clangif (is_win) {cflags = [ "/O2" ] # MSVC}
}
条件化包含文件
static_library("math_utils") {sources = ["src/math/core.cc",]# 仅在启用扩展时包含高级功能if (enable_advanced_math) {sources += [ "src/math/advanced.cc" ]}
}
4. 构建与验证
生成构建文件
gn gen out/Default
编译
ninja -C out/Default calculator
验证符号
# Linux/macOS 查看静态库内容
ar -t out/Default/obj/src/math/math_utils.a# Windows 查看 .lib 内容(使用 dumpbin)
dumpbin /HEADERS out/Default/obj/src/math/math_utils.lib
5. 与 source_set 的对比
| 特性 | static_library | source_set |
|---|---|---|
| 输出文件 | 生成 .a 或 .lib | 无输出文件 |
| 编译次数 | 仅编译一次,重复链接 | 每个依赖目标独立编译 |
| 适用场景 | 代码规模较大,需复用编译结果 | 小型模块、需不同编译选项的代码 |
| 依赖传递 | 通过链接合并到最终目标 | 代码直接编译到依赖目标中 |
| 编译速度 | 较快(代码仅编译一次) | 可能较慢(重复编译) |
总结
通过 static_library 可以:
- 模块化复用代码:将常用功能封装成库。
- 减少编译时间:避免重复编译公共代码。
- 优化代码管理:分层依赖,提升项目结构清晰度。
典型应用场景包括:
- 核心算法库(如加密、数学计算)。
- 跨平台适配层(如抽象系统 API)。
- 第三方代码集成(如将开源库构建为静态库)。
10. 举例说明pool
在 GN 构建系统中,pool 用于 限制任务的并发执行数量,控制构建过程中特定类型任务的资源使用。它特别适用于以下场景:
- 资源密集型任务(如链接、代码生成)
- 单线程工具(如某些代码生成器)
- 避免内存溢出(限制并行任务数量)
1. 定义 Pool
在 .gn 文件或 BUILD.gn 中定义 pool:
# 定义一个名为 "heavy_tasks" 的池,最多同时运行 2 个任务
pool("heavy_tasks") {depth = 2 # 并发数限制
}
2. 使用 Pool 控制任务
(1) 限制链接任务并发数
# 将链接任务分配到 "heavy_tasks" 池
executable("my_app") {sources = [ "main.cc" ]deps = [ "//libs:core" ]pool = ":heavy_tasks" # 关键配置
}
(2) 串行化单线程工具
# 代码生成工具只能单线程运行
action("generate_code") {script = "scripts/generate.py"inputs = [ "template.json" ]outputs = [ "$target_gen_dir/generated.cc" ]pool = ":single_thread_pool" # 限制并发为 1
}# 定义单线程池
pool("single_thread_pool") {depth = 1
}
3. 内置 Pool
GN 提供两个内置池:
default:默认池(深度为并发线程数,通常等于 CPU 核心数)console:用于需要独占控制台的任务(深度为 1)
强制串行执行测试
test("integration_test") {sources = [ "test.cc" ]pool = ":console" # 测试按顺序运行
}
4. 完整示例
项目结构
project/
├── .gn
├── BUILD.gn
├── src/
│ └── main.cc
└── tools/└── generate.py
BUILD.gn 配置
# 定义资源池
pool("heavy_tasks") {depth = 2 # 同时最多运行 2 个链接任务
}# 代码生成器池(单线程)
pool("codegen_pool") {depth = 1
}# 代码生成操作
action("generate_headers") {script = "//tools/generate.py"inputs = [ "config.json" ]outputs = [ "$target_gen_dir/data.h" ]pool = ":codegen_pool" # 强制串行生成
}# 主程序
executable("app") {sources = [ "src/main.cc" ]deps = [ ":generate_headers" ]pool = ":heavy_tasks" # 限制链接并发
}
5. 验证 Pool 效果
运行构建命令时,观察任务并发数:
ninja -C out/Default -j 8 # 总并发数设为 8# 输出示例:
# [2/8] LINK app # heavy_tasks 池限制为 2 个链接任务
# [1/8] ACTION generate_headers # codegen_pool 池串行运行
总结
| 场景 | Pool 配置 | 效果 |
|---|---|---|
| 内存敏感型链接任务 | depth = N(如 2) | 防止 OOM |
| 单线程代码生成工具 | depth = 1 | 避免竞态条件 |
| 控制台独占任务 | 内置 console 池 | 确保日志顺序 |
通过合理使用 pool,可以显著优化大型项目的构建稳定性和效率。
11. 举例说明template
在 GN 构建系统中,template 用于 定义可复用的构建规则模板,减少重复代码,统一管理配置。以下是详细用法和跨平台示例:
1. 基本语法
定义模板
# 定义一个名为 "component" 的模板
template("component") {# 内部使用 invoker 访问传入参数static_library(target_name) {sources = invoker.sourcesdeps = invoker.deps# 公共配置configs = [ "//build:common_config" ]}
}
使用模板
# 调用模板生成静态库
component("math_utils") {sources = [ "math.cc" ]deps = [ "//base" ]
}
2. 多目标模板示例
场景
需要为每个组件生成:
- 一个静态库
- 一个测试可执行文件
- 一个文档生成任务
模板定义 (BUILD.gn)
template("component") {# 参数校验assert(defined(invoker.name), "必须指定组件名称")assert(defined(invoker.sources), "必须指定源文件")# 生成静态库static_library(invoker.name) {sources = invoker.sourcesdeps = invoker.deps + [ "//base" ]configs = [ "//build:strict_warnings" ]}# 生成测试可执行文件(可选)if (invoker.enable_tests) {executable(invoker.name + "_tests") {testonly = truesources = invoker.test_sourcesdeps = [ ":" + invoker.name ]}}# 生成文档(可选)if (invoker.gen_docs) {action(invoker.name + "_docs") {script = "//tools/generate_docs.py"inputs = invoker.sourcesoutputs = [ "$target_gen_dir/docs/" + invoker.name + ".md" ]}}
}
调用模板
component("math_utils") {name = "math" # 模板要求必须的参数sources = ["math.cc","math.h",]enable_tests = truetest_sources = [ "math_test.cc" ]gen_docs = true
}
3. 高级用法
(1) 嵌套模板
template("base_component") {# 基础配置component(target_name) {# 覆盖或扩展参数deps = invoker.deps + [ "//core" ]}
}
(2) 条件化配置
template("platform_component") {static_library(target_name) {sources = invoker.sourcesif (is_win) {defines = [ "WINDOWS_API" ]} else {defines = [ "POSIX_API" ]}}
}
(3) 自动生成目标名称
template("auto_naming") {# 根据目录生成目标名target_name = get_path_info(".", "name") + "_lib"static_library(target_name) {sources = invoker.sources}
}
4. 实际项目结构
project/
├── build/
│ └── config.gni # 公共配置
├── src/
│ ├── math/
│ │ ├── math.cc
│ │ ├── math.h
│ │ └── BUILD.gn # 调用 component 模板
│ └── network/
│ └── BUILD.gn # 调用另一个模板
└── tools/└── generate_docs.py
5. 模板参数说明
| 参数 | 说明 | 是否必填 |
|---|---|---|
name | 组件名称(决定目标名前缀) | 是 |
sources | 组件源码文件列表 | 是 |
enable_tests | 是否生成测试可执行文件 | 否 |
test_sources | 测试代码文件列表(需 enable_tests=true) | 否 |
gen_docs | 是否生成文档 | 否 |
6. 验证模板输出
生成构建文件后检查目标列表:
gn ls out/Default
# 输出示例:
//src/math:math
//src/math:math_tests
//src/math:math_docs
总结
通过 template 可以实现:
- 代码复用:统一管理相似目标的配置
- 规范约束:强制参数校验,避免配置遗漏
- 灵活扩展:通过条件判断支持可选功能
典型应用场景包括:
- 标准化组件构建流程
- 自动化生成测试/文档任务
- 跨平台差异化配置
12. 举例说明windows环境下的tool和toolchain
deepseek给出的答案错误用法太多了,windows环境下的编译工具包括MSVC、Clang-cl、MinGW,下面是我自己调试出可以运行的MSVC代码。
在 Windows 环境下,工具链和工具的配置需要适配 Windows 的编译工具(如 MSVC、Clang-cl、MinGW 等)。以下是针对 Windows 的详细示例:
1. 项目结构
project/
├── .gn
├── build/
│ ├── config/
│ │ └── BUILDCONFIG.gn
├── buildtools/
│ ├── …
├── toolchains/
│ ├── msvc/
│ │ └── BUILD.gn # MSVC 工具链
└── src/
├── hello-world.cc
└── BUILD.gn
(1)BUILDCONFIG.gn
set_default_toolchain("//build/toolchains/msvc:msvc_toolchain")
is_win = current_os == "win" || current_os == "winuwp"
(2)src/BUILD.gn
# 使用 MSVC 工具链构建可执行程序
executable("app_msvc") {sources = [ "hello-world.cc" ]
}
(3)src/hello-world.cc
#include <iostream>
using namespace std;int main()
{cout<< "hello world" << endl;while(1) {}return 0;
}
(4)build/toolchains/msvc/BUILD.gn
toolchain("msvc_toolchain") {#cl.exe和link.exe所在目录的路径prefix = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.43.34808/bin/Hostx64/x64/"tool("cxx") { # C++ 编译器cl = "cl.exe"# 各种头文件dirs = ["C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\include","C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\ATLMFC\include","C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include","C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt","C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\um","C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\shared","C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\winrt","C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\cppwinrt"]sys_include_flags = ""# 将[a, b]转化为"/Ia /Ib"的形式foreach(dir, dirs) {sys_include_flags += "/I\"${dir}\" "}# $prefix$cl cl.exe的绝对路径# /c 仅编译不链接# Fo<file> 指定输出的.obj文件名# $sys_include_flags 即/I<dir> 添加头文件搜索路径command = "$prefix$cl /c {{source}} /Fo{{output}} $sys_include_flags"description = "CXX {{output}}"outputs = [ "{{target_out_dir}}/{{label_name}}/{{source_name_part}}.obj" ]}# 定义链接器(link.exe)tool("link") {exename = "{{output_dir}}/{{target_output_name}}{{output_extension}}"link= "link.exe"# 相关静态库dirs = ["C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.43.34808/lib/x64","C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/um/x64","C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/ucrt/x64",]sys_include_flags = ""foreach(dir, dirs) {sys_include_flags += "/LIBPATH:\"${dir}\" "}# $prefix$link 指定link.exe绝对路径# /OUT:<file> 指定输出的可执行文件或动态库文件名# $sys_include_flags 即/LIBPATH:<dir> 添加静态库搜索路径# {{inputs_newline}} 值为编译阶段输出的obj文件,但是link阶段接收不到source_name_part变量,所以需要用inputs_newlinecommand = "$prefix$link \"/OUT:$exename\" $sys_include_flags {{inputs_newline}}"default_output_extension = ".exe"default_output_dir = "{{root_out_dir}}"description = "LINK {{output}}"outputs = [exename,]runtime_outputs = outputs}
}
总结:Windows 工具链核心元素
| 工具类型 | 典型命令 | 作用 | Windows 特有参数 |
|---|---|---|---|
cc | cl.exe / gcc | 编译 C 代码 | /Fo, /D, /I |
cxx | cl.exe / g++ | 编译 C++ 代码 | /std:c++20, /EHsc |
link | link.exe / g++ | 链接可执行文件 | /SUBSYSTEM, /LIBPATH |
solink | link.exe / g++ | 形成动态库/共享库文件 | /DLL, /IMPLIB |
alink | lib.exe / ar | 生成静态库 | /OUT, rcs |
rc | rc.exe | 编译资源文件 | /fo, /d |
mt | mt.exe | 嵌入清单文件 | -manifest, -outputresource |
13. .obj文件的作用
在 Windows 平台下的 C/C++ 开发中,.obj 文件(Object File,目标文件)是 编译器生成的中间文件,用于存储 编译后的机器代码、符号表 和 重定位信息。以下是其核心作用的详细说明:
1. 模块化编译的产物
- 作用:每个
.c/.cpp源文件会被 独立编译 为.obj文件,实现 增量编译,避免重复编译未修改的代码。 - 优势:大型项目中,修改单个文件只需重新编译对应的
.obj文件,提升编译效率。
2. 存储编译后的机器代码
- 内容:
.obj文件包含源文件编译后的 二进制指令(机器码),但尚未链接为完整可执行文件。 - 示例:
编译为// main.cpp int add(int a, int b) { return a + b; }main.obj后,会包含add函数的二进制指令。
3. 符号表(Symbol Table)
- 作用:记录代码中 函数 和 全局变量 的 名称 和 地址,供链接器解析跨文件的引用。
- 关键符号类型:
- 导出符号(Export Symbols):当前模块定义的函数/变量(如
add)。 - 未解析符号(Undefined Symbols):当前模块引用但未定义的函数/变量(如外部库函数
printf)。
- 导出符号(Export Symbols):当前模块定义的函数/变量(如
4. 重定位信息(Relocation Data)
- 作用:标记代码中需要 链接器修正的地址(如全局变量地址、函数调用地址),在链接阶段填充实际地址。
- 示例:
// main.cpp extern int global_var; // 声明外部变量 int main() { return global_var; }main.obj中的global_var地址为临时值,链接时由其他.obj或库文件提供实际地址。
5. 调试信息(可选)
- 作用:若编译时启用调试(
/Zi或/Z7),.obj文件会包含 源代码行号、变量类型 等信息,用于生成 PDB 文件(Program Database)。 - 调试流程:调试器通过 PDB 文件将机器码映射回源代码。
6. 链接器(Linker)的输入
- 链接过程:链接器(如
link.exe)将多个.obj文件和静态库(.lib)合并,生成最终的可执行文件(.exe)或动态库(.dll)。 - 关键任务:
- 符号解析:匹配所有
.obj文件中的导出符号和未解析符号。 - 地址重定位:修正代码中的地址引用。
- 合并段(Sections):将代码段(
.text)、数据段(.data)等按规则合并。
- 符号解析:匹配所有
7. 跨平台对比
| 平台 | 目标文件扩展名 | 链接器 | 工具链示例 |
|---|---|---|---|
| Windows | .obj | link.exe | MSVC, MinGW |
| Linux/macOS | .o | ld | GCC, Clang |
8. 常见问题
Q1:链接时出现 “未解析的外部符号” 错误?
- 原因:某个
.obj文件缺少符号定义(如未实现函数,或未链接对应库)。 - 解决:检查所有
.obj文件和.lib是否包含所需符号。
Q2:如何直接使用 .obj 文件?
- 场景:复用第三方编译好的代码模块。
- 方法:在项目中直接链接
.obj文件:link.exe main.obj third_party.obj /OUT:app.exe
Q3:.obj 文件与静态库(.lib)的关系?
- 静态库:本质是多个
.obj文件的打包集合,可通过lib.exe工具创建:lib.exe /OUT:mylib.lib file1.obj file2.obj
总结
.obj 文件是 编译到链接的关键中间产物,承载了机器码、符号和重定位信息,使得模块化开发和高效链接成为可能。理解其作用有助于诊断编译错误、优化构建流程,并深入掌握底层开发机制。
13. rspfile作用
.rsp 文件(响应文件)的作用
.rsp 文件(Response File,响应文件)是 一种将命令行参数存储在文本文件中的机制,用于解决命令行过长或参数复杂度过高的问题。它广泛应用于编译(cl.exe)、链接(link.exe)等工具中,尤其在大型项目中管理大量文件或复杂参数时非常关键。
核心作用
-
突破命令行长度限制
Windows 系统的命令行参数最大长度为 8191 字符。当参数过多(如包含数百个源文件或库文件)时,直接通过命令行传递会报错,而响应文件可绕过此限制。 -
简化复杂参数管理
将重复使用的参数(如包含路径、库路径)集中管理,避免重复输入。 -
支持参数复用
同一响应文件可被多个构建命令共享,提升维护效率。
使用场景
- 编译阶段:传递大量源文件(
.cpp)、头文件路径(/I)或宏定义(/D)。 - 链接阶段:传递大量库文件(
.lib)、库路径(/LIBPATH)或链接选项。 - 构建系统(如 GN、CMake):自动生成响应文件以简化构建命令。
语法规则
-
参数分隔方式
- 换行分隔:每行一个参数(推荐,避免空格问题)。
# my_config.rsp /Iinclude /DDEBUG main.cpp util.cpp - 空格分隔:参数之间用空格分隔(需注意路径中的空格需用引号包裹)。
# my_config.rsp "/IProgram Files/include" /DDEBUG main.cpp util.cpp
- 换行分隔:每行一个参数(推荐,避免空格问题)。
-
引用方式
在命令行中使用@符号引用响应文件:cl.exe @my_config.rsp link.exe @link_options.rsp -
嵌套引用
响应文件内可嵌套引用其他响应文件(需工具支持):# link_options.rsp /LIBPATH:libs @libs_list.rsp # 嵌套引用
示例
1. 编译时使用响应文件
- 响应文件
sources.rsp:/Iinclude /DDEBUG /EHsc /c main.cpp util.cpp network.cpp - 命令行调用:
cl.exe @sources.rsp
2. 链接时使用响应文件
- 响应文件
link.rsp:/OUT:app.exe /SUBSYSTEM:CONSOLE main.obj util.obj user32.lib kernel32.lib - 命令行调用:
link.exe @link.rsp
注意事项
-
路径中的空格
若路径包含空格,需用双引号包裹路径:"/IProgram Files/My Project/include" -
编码格式
响应文件需保存为 ANSI 或 UTF-8 无 BOM 格式,避免工具解析错误。 -
工具兼容性
不同工具对响应文件的解析规则可能略有差异(如换行符处理)。
高级用法
在构建系统中自动生成响应文件
例如,在 GN 构建系统中,通过 write_file 生成响应文件:
# 生成 sources.rsp
write_file("sources.rsp", ["/Iinclude","/DDEBUG","main.cpp","util.cpp",
])# 编译命令
executable("my_app") {command = "cl.exe @sources.rsp"
}
总结
| 场景 | 作用 | 示例 |
|---|---|---|
| 参数过多 | 突破命令行长度限制 | cl.exe @files.rsp |
| 复杂参数管理 | 集中维护常用参数 | 定义通用路径、宏 |
| 构建系统集成 | 自动化生成和引用 | GN/CMake 生成 .rsp 文件 |
通过合理使用响应文件,可以显著提升大型项目的构建效率和可维护性。
14. /Fd的作用
/Fd 参数的作用
在 MSVC 编译器 (cl.exe) 中,/Fd 参数用于 指定生成的 PDB(Program Database)调试文件的路径和名称。PDB 文件包含程序的调试信息(如符号表、源代码映射等),是调试 Release/Debug 版本程序的关键文件。
参数格式
/Fd[<路径>/]<文件名>
- 路径:可选,指定 PDB 文件的输出目录(若省略,默认输出到当前目录或与
.obj文件同目录)。 - 文件名:指定 PDB 文件的名称(默认情况下,编译器会生成名为
vc<版本>.pdb的文件)。
使用场景
1. 自定义 PDB 文件名
默认情况下,编译器会生成类似 vc140.pdb 的文件,但多个项目或配置可能产生冲突。通过 /Fd 指定唯一名称:
cl /Zi /Fd"MyApp.pdb" main.cpp
生成的 PDB 文件名为 MyApp.pdb。
2. 控制 PDB 输出路径
将 PDB 文件集中输出到指定目录(便于调试管理):
cl /Zi /Fd"bin/Debug/MyApp.pdb" main.cpp
3. 多配置构建(Debug/Release)
不同构建配置使用不同的 PDB 文件:
# Debug 配置
cl /Zi /Fd"bin/Debug/MyApp_Debug.pdb" /DEBUG main.cpp# Release 配置
cl /Zi /Fd"bin/Release/MyApp_Release.pdb" /O2 main.cpp
4. 防止多个编译器进程写入同一个 PDB 文件
当使用 /MP(多进程编译) 时,多个编译进程可能同时写入同一个 PDB 文件,导致冲突。通过 /Fd 为每个进程生成独立 PDB 文件:
cl /MP4 /Zi /Fd"obj/" main.cpp
编译器会在 obj/ 目录下生成多个 PDB 文件(如 main.pdb, util.pdb),最后链接器会自动合并它们。
注意事项
-
依赖
/Zi或/ZI:
只有启用调试信息生成(/Zi或/ZI)时,/Fd才会生效。例如:cl /Zi /Fd"MyApp.pdb" main.cpp # 生效 cl /Fd"MyApp.pdb" main.cpp # 不生效(未启用调试信息) -
与链接器生成的 PDB 文件的关系:
/Fd控制的是编译器生成的 PDB 文件(包含中间调试信息,通常较小)。- 链接器生成的 PDB 文件(包含完整调试信息)需通过链接器的
/PDB参数指定:link /DEBUG /PDB:"bin/MyApp_Full.pdb" main.obj
-
路径合法性:
确保指定的输出目录存在,否则编译器会报错:cl /Zi /Fd"non_exist_dir/MyApp.pdb" main.cpp # 错误:路径不存在
完整示例
# 编译阶段:生成中间 PDB 文件
cl /Zi /Fd"obj/intermediate.pdb" /c main.cpp util.cpp# 链接阶段:生成最终 PDB 文件
link /DEBUG /PDB:"bin/app.pdb" /OUT:"bin/app.exe" main.obj util.obj
总结
- 核心作用:自定义编译器生成的中间 PDB 文件的名称和路径。
- 适用场景:多项目构建、调试配置管理、多进程编译防冲突。
- 配合参数:
/Zi(启用调试信息)、/MP(多进程编译)、链接器的/PDB。
15. gn文件中$变量和{{}}变量有什么区别
在 GN 构建系统 中,$变量 和 {{变量}} 是两种不同的变量引用方式,主要区别在于 作用域 和 使用场景。以下是具体分析:
1. $变量:直接变量引用
- 作用:在 当前作用域 或 父作用域 中直接引用已定义的变量。
- 特点:
- 静态解析:在代码编写时确定变量的值。
- 作用域透明:自动向上查找父作用域的变量。
- 适用场景:普通变量赋值、字符串拼接、条件表达式等。
- 示例:
# 定义变量 my_var = "hello"# 直接引用 print("Value is: $my_var") # 输出 "Value is: hello"# 在作用域中覆盖变量 if (condition) {my_var = "world"print("New value: $my_var") # 输出 "New value: world" }
2. {{变量}}:模板参数传递
- 作用:在 模板(Template) 中定义动态参数,用于接收外部传入的值。
- 特点:
- 动态绑定:在模板实例化时确定变量的值。
- 作用域隔离:仅在模板内部生效,不污染外部作用域。
- 适用场景:定义可复用的模板规则(如库、可执行文件等)。
- 示例:
# 定义一个模板 template("my_template") {# 模板参数通过 {{}} 接收外部值target_name = "{{target_name}}"sources = [ "{{input_file}}" ]# 在模板中使用参数executable(target_name) {sources = sources} }# 调用模板并传递参数 my_template("my_app") {input_file = "main.cpp" }
3. 核心区别总结
| 特性 | $变量 | {{变量}} |
|---|---|---|
| 作用域 | 当前或父作用域 | 模板内部作用域 |
| 解析时机 | 静态(代码编写时) | 动态(模板实例化时) |
| 主要用途 | 普通变量引用、字符串拼接 | 模板参数传递、动态配置 |
| 可见性 | 全局或局部作用域 | 仅限模板内部 |
4. 常见误区
{{}}不是 GN 的通用语法:
它仅在 模板定义 中用于参数传递,不能直接在普通作用域中使用。$变量无法穿透模板作用域:
模板内部无法直接通过$变量访问外部变量,必须显式传递参数。
5. 高级用法
(1) 混合使用 $ 和 {{}}
在模板中,可以通过 $变量 引用模板外的变量,而 {{}} 用于接收动态参数:
template("advanced_template") {# 静态引用外部变量external_var = "External: $some_global_var"# 动态接收参数target_name = "{{target_name}}"executable(target_name) {sources = [ "src/$target_name.cpp" ]}
}
(2) 模板参数默认值
通过 {{变量=默认值}} 定义带默认值的参数:
template("my_template") {# 默认值为 "app"target_name = "{{target_name=app}}"executable(target_name) { ... }
}# 不传参时使用默认值
my_template() # 生成名为 "app" 的可执行文件
总结
$变量:用于 静态作用域 的变量引用,直接解析当前或父作用域的值。{{变量}}:用于 模板参数传递,实现动态配置和复用逻辑。
理解两者的区别,可以避免作用域污染和模板参数传递错误,从而更高效地编写 GN 构建脚本。
16. 如何获取{{}}变量值
好的,现在我需要帮助用户解决在GN构建系统中查看模板变量值的问题。用户已经尝试了之前提到的几种方法,但似乎都没有效果,所以需要进一步思考其他可能性。
首先,回顾用户的问题:用户在使用GN构建系统时,无法通过之前提到的方法(如print、write_file、gn desc等)查看模板中{{变量}}的实际值。用户希望找到其他途径来获取这些变量的值。
接下来,我需要分析可能的原因和解决方案。用户提到的方法都不起作用,可能是因为环境配置问题、GN版本问题,或者变量在特定情况下未被正确解析。另外,可能存在一些用户未注意到的细节,比如作用域问题或变量覆盖。
首先,考虑是否有其他调试方法。例如,GN的跟踪功能,或者更底层的日志输出。此外,可能需要检查GN的版本是否过旧,导致某些功能不可用。另外,用户是否在正确的作用域中调用了模板,变量是否被正确传递。
另一个可能性是,用户可能在处理复杂的模板嵌套时,变量在传递过程中被覆盖或修改,导致难以追踪。此时,可能需要检查模板的调用链,确保变量在每一步都正确传递。
此外,用户可能没有正确使用GN的语法,例如变量的引用方式是否正确,或者在模板定义中是否存在语法错误,导致变量未被正确展开。
接下来,考虑可能的解决方案。首先,建议用户检查GN的版本,确保是最新版本。其次,使用更详细的日志输出,例如通过设置GN的–verbose标志,查看详细的生成过程。此外,可以尝试在模板外部定义变量,或者使用断言来验证变量的存在性。
如果以上方法都不奏效,可能需要更深入的分析,例如手动解析生成的ninja文件,查找变量展开后的痕迹,或者创建最小的测试用例来复现问题,从而隔离问题所在。
最后,确保用户正确理解了GN模板的工作机制,包括变量的作用域和生命周期,以及模板实例化的过程。可能需要提供一些示例代码,帮助用户验证他们的配置是否正确。
总结起来,需要从多个角度切入,包括环境检查、工具使用、语法验证和问题隔离,以帮助用户找到根本原因并解决问题。
如果常规方法无法查看 GN 模板中的 {{变量}} 值,可能是由于模板设计或作用域限制导致的。以下是更深入的高级调试方案,适用于复杂场景或工具链限制:
方案 1:强制生成错误日志
通过 故意触发错误,让 GN 在报错时暴露变量值(利用错误信息的上下文)。
步骤:
-
在模板中插入一条 依赖变量值的错误条件:
template("my_template") {my_var = "{{my_var}}"assert(my_var != "", "my_var 的值是: " + my_var) } -
调用模板时传递空值(或非法值):
my_template("test") {my_var = "" } -
运行
gn gen,触发断言错误并输出变量值:ERROR at //BUILD.gn:3:5: Assertion failed. assert(my_var != "") # my_var 的值是: Hello, GN!
方案 2:劫持 action 目标生成中间文件
利用 action 目标将变量值写入构建中间文件。
步骤:
-
在模板中添加
action,将变量值写入文件:template("my_template") {my_var = "{{my_var}}"action("dump_var") {script = "//build/dump_var.py"inputs = []outputs = [ "$target_gen_dir/dump.txt" ]args = [ "--value", my_var ] # 将变量传递给脚本} } -
创建 Python 脚本
dump_var.py:import argparse parser = argparse.ArgumentParser() parser.add_argument("--value") args = parser.parse_args() with open("dump.txt", "w") as f:f.write(args.value) -
构建目标并查看文件:
ninja -C out/Debug dump_var cat out/Debug/gen/dump.txt
方案 3:自定义 GN 跟踪函数
通过修改 GN 的 Python 源码或注入自定义跟踪函数,实时记录变量值。
步骤:
- 定位 GN 的 Python 源码(通常位于
gn.py或工具链相关文件)。 - 在模板处理逻辑中添加跟踪代码:
注意:此方法需熟悉 GN 源码结构,风险较高,建议备份后操作。# 在 gn 的 template 处理代码中插入 def Template_Handle(...):print(f"模板变量 {{my_var}} 的值是: {current_scope.GetValue('my_var')}")# 原有逻辑...
方案 4:利用 Ninja 的 -d keepdepfile 选项
通过分析 Ninja 的依赖文件,间接推导变量值。
步骤:
-
在 GN 模板中生成依赖文件:
action("my_action") {script = "//build/my_script.py"depfile = "$target_gen_dir/my_action.d"args = [ "{{my_var}}" ] # 将变量传递给脚本 } -
构建时保留依赖文件:
ninja -C out/Debug -d keepdepfile my_action -
查看生成的
.d文件:cat out/Debug/gen/my_action.d # 输出可能包含 my_var 的值
方案 5:反向工程生成器输出
解析 GN 生成的 build.ninja 或 IDE 项目文件,反推变量值。
示例:
-
生成构建文件:
gn gen out/Debug -
搜索与模板目标相关的规则:
grep -r "my_template_target" out/Debug -
分析规则中的命令行参数或依赖项,推断变量值。
总结
| 方案 | 适用场景 | 优点 | 风险/复杂度 |
|---|---|---|---|
| 强制生成错误日志 | 快速调试简单变量 | 无需额外脚本,直接输出 | 需手动触发错误 |
劫持 action 目标 | 需要持久化记录变量值 | 灵活,可定制输出格式 | 需编写外部脚本 |
| 修改 GN 源码 | 深度调试工具链内部逻辑 | 直接访问底层数据 | 高风险,需源码知识 |
| 分析 Ninja 依赖文件 | 需要间接推导变量值 | 利用现有构建工具 | 结果可能模糊 |
| 反向工程生成器输出 | 无法修改模板时的应急方案 | 无需改动代码 | 结果可能不直观 |
如果以上方案仍无法解决问题,建议 提供完整的模板代码和调用上下文,以便进一步分析隐藏的变量传递逻辑或作用域问题。
相关文章:
gn学习存档
以下答案均由deepseek提供,仅作学习存档。 1. 举例说明action和action_foreach区别 场景设定 假设需要处理一组文件: 输入文件:src/data/file1.txt, src/data/file2.txt, src/data/file3.txt处理逻辑:将每个 .txt 文件转换为 …...
扩展Windows磁盘的技术型教学文章
在Windows操作系统中,扩展磁盘是一项常见的任务,特别是当您需要增加存储空间以容纳更多数据时。以下是一份技术性的教学文章,介绍如何在Windows系统中扩展磁盘: #### 步骤一:打开磁盘管理器 1. 按下 Win X 组合键&a…...
【每日学点HarmonyOS Next知识】web滚动、事件回调、selectable属性、监听H5内部router、Grid嵌套时高度设置
【每日学点HarmonyOS Next知识】web滚动、事件回调、selectable属性、监听H5内部router、Grid嵌套时高度设置 1、HarmonyOS WebView加载url无法滚动? scroll 里面嵌套webView,demo参考: // xxx.ets import web_webview from ohos.web.webv…...
(十 四)趣学设计模式 之 策略模式!
目录 一、 啥是策略模式?二、 为什么要用策略模式?三、 策略模式的实现方式四、 策略模式的优缺点五、 策略模式的应用场景六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支…...
React:Router路由
ReactRouter引入 在index.js里编辑,创建路由,绑定路由 import React from react; import ReactDOM from react-dom/client; import ./index.css; import reportWebVitals from ./reportWebVitals; import { createBrowserRouter, RouterProvider } from…...
解决docker认证问题 failed to authorize: failed to fetch oauth token
报错信息[bash1]解决方案 全局代理打开“buildkit”: false ,见[图1] [bash1] >docker build -t ffpg . [] Building 71.8s (3/3) FINISHED docker:desktop-linux> [internal] load bui…...
解决Vscode项目同时运行两个项目终端无法自动叠加的问题
终端(如命令行工具或服务进程)无法自动“叠加”使用同一资源(如端口号、进程ID等)的核心原因在于操作系统的资源管理机制和网络协议规范的限制。以下是具体分析: 以下是解决 VSCode 同时运行两个项目时终端被前一个占…...
mybatis映射文件相关的知识点总结
mybatis映射文件相关的知识点总结 mybatis官网地址 英文版:https://mybatis.org/mybatis-3/index.html 中文版:https://mybatis.p2hp.com/ 搭建环境 /* SQLyog Ultimate v10.00 Beta1 MySQL - 8.0.30 : Database - mybatis-label *****************…...
verilog 基本语法结构与定义
1、词法约定 verilog中的基本词法约定与C类似。verilog描述包含一个 单词流 ,这里 单词 可以是 注释、分隔符、数字、字符串、标识符、关键字。 verilog大小写相关,其中关键字全部小写。 1.1、 空白符 空白符有空格(\b)制表…...
最节省成本的架构方案:无服务器架构
无服务器架构(Serverless Architecture)是一种颠覆性的云计算执行模型,它允许开发者专注于编写和部署代码,而无需担心底层服务器基础设施的管理。这种架构以其按需付费、自动伸缩和简化部署等优势,在成本优化方面表现出…...
docker利用docker-compose-gpu.yml启动RAGFLOW,文档解析出错【亲测已解决】
0.问题说明 想要让RAGFLOW利用GPU资源跑起来,可以选择docker-compose-gpu.yml启动。(但是官网启动案例是86平台的不是NVIDIA GPU的,docker-compose-gpu.yml又是第三方维护,所以稍有问题) 1.问题 docker利用docker-c…...
字节跳动系统攻防算法岗-Flow安全内推
熟悉Android/iOS/鸿蒙/Windows/Linux中的任意一种及多种安全架构,并有漏洞产出; 熟练掌握硬件及BSP安全技术,包括主流SoC安全特性、可信链、TEE等相关技术原理; 熟悉行业主流的安全标准、安全模型、安全解决方案、安全体系&…...
一些有用的电子书
一、Python 1.1、流畅的Python 链接:https://www.aliyundrive.com/s/S4sr1RNXehW 1.2、Python自动化运维与技术实战 链接:https://www.aliyundrive.com/s/dAQQhbyWp3c 1.3、疯狂Python讲义 链接:https://www.aliyundrive.com/s/NnUqjzGB1az …...
TMS320F28P550SJ9学习笔记1:CCS导入工程以及测试连接单片机仿真器
学习记录如何用 CCS导入工程以及测试连接单片机仿真器 以下为我的CCS 以及驱动库C2000ware 的版本 CCS版本: Code Composer Studio 12.8.1 C2000ware :C2000Ware_5_04_00_00 目录 CCS导入工程: 创建工程: 添加工程: C…...
Android中使用Robolectric测试点击事件(不需要手机)
文章目录 一、前言二、简单示例三、注意事项四、另一种写法五、拓展六、参考文档 一、前言 Robolectric 是一个由 Google 维护的开源 Android 测试框架,它允许你以 Android 运行时环境运行单元测试。 Robolectric 提供了一个模拟 Android 运行时环境,允…...
安卓开发相机功能
相机功能 安卓中的相机调用功能也经历了很多的方案升级,目前可选的官方方案是CameraX、Camera2、Camera(废弃),还有一些第三方免费或者是付费的相机库。对于大多数开发者,建议使用 CameraX。 CameraX CameraX 是 An…...
机器学习:监督学习、无监督学习和强化学习
机器学习(Machine Learning, ML)是人工智能(AI)的一个分支,它使计算机能够从数据中学习,并在没有明确编程的情况下执行任务。机器学习的核心思想是使用算法分析数据,识别模式,并做出…...
基于vue3和flask开发的前后端管理系统(一):项目启动准备
准备工作 我们需要准备以下工具 vue3:构建前端 tailwind css:样式库vite:快速构建vue项目pinia :vue3 的事件管理器 flask:后端代码Mysql:数据库 heidisql:数据库图形化界面 vscode࿱…...
一、MySQL备份恢复
一、MySQL备份恢复 1.1 MySQL日志管理 数据库中数据丢失或被破坏可能原因 误删除数据库 数据库工作时,意外断电或程序意外终止 由于病毒造成的数据库损坏或丢失 文件系统损坏后,系统进行自检操作 升级数据库时,命令语句不严格 设备故…...
DeepSeek崛起:如何在云端快速部署你的专属AI助手
在2025年春节的科技盛宴上,DeepSeek因其在AI领域的卓越表现成为焦点,其开源的推理模型DeepSeek-R1擅长处理多种复杂任务,支持多语言处理,并通过搜索引擎获取实时信息。DeepSeek因其先进的自然语言处理技术、广泛的知识库和高性价比…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...
字符串哈希+KMP
P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...
