一、下载编译-windows build
| 工具 | 要求 | 安装方式 |
|---|---|---|
| Visual Studio 2022 | 版本 17.13 或更高必须选 "Desktop development with C++" workload必须安装 ClangCL 组件:- C++ Clang Compiler for Windows (Microsoft.VisualStudio.Component.VC.Llvm.Clang)- MSBuild support for LLVM toolset (Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset) | 下载地址:https://visualstudio.microsoft.com/downloads/安装时勾选 "C++ 桌面开发"如果已装,打开 Visual Studio Installer → 修改 → 单个组件 → 搜索 "Clang" 勾选上面两个 |
| Python 3 | 3.12+(推荐 Microsoft Store 版) | Microsoft Store 搜索 "Python" 安装最新版,确保在 PATH 中(命令行能运行 python --version) |
| Git for Windows | 包含 Git Bash 和 Unix tools(必须加到 PATH) | https://git-scm.com/download/win |
| NASM(可选,但推荐) | 用于 OpenSSL 汇编优化(如果不装,加 --openssl-no-asm 参数) | 下载 https://www.nasm.us/ ,解压后加到 PATH 这个我最开始没有设置path ,就默认装c盘了 |
常见坑:如果缺少 ClangCL,编译会直接报错"missing components"。解决:卸载重装上面两个 Clang 组件。2. 进入源码目录并切换分支(你已经好了这一步)
powershell
powershell
git clone https://github.com/nodejs/node.git
cd node # 进入你 clone 的目录
git checkout v25.x # 推荐这个最新 Current 分支
git pull # 拉取最新代码
注意:源码路径不能有空格或中文(比如别放在 C:\Users\你的名字\),否则编译失败!建议放在 C:\node 或 D:\node。3. 编译命令(核心步骤)打开 x64 Native Tools Command Prompt for VS 2022(开始菜单搜索 "x64 Native"),或者用 Git Bash/PowerShell。最简单命令(Release 模式,默认):
powershell
powershell
.\vcbuild.bat
- 编译成功后,可执行文件在:out\Release\node.exe
先贴一个git的链接 https://github.com/theanarkh/understand-nodejs/blob/master/docs/chapter01-Node.js组成和原理.md
二、基本路径
node/src/node_main.cc
python
int main(int argc, char* argv[]) {
return node::Start(argc, argv);
}
#endif
======>
libnode/src/node.cc
python
int Start(int argc, char** argv) {
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
std::tie(argc, argv) = sea::FixupArgsForSEA(argc, argv);
#endif
return static_cast<int>(StartInternal(argc, argv));
}
int Stop(Environment* env, StopFlags::Flags flags) {
env->ExitEnv(flags);
return 0;
}
} // namespace node
#if !HAVE_INSPECTOR
void Initialize() {}
NODE_BINDING_CONTEXT_AWARE_INTERNAL(inspector, Initialize)
#endif // !HAVE_INSPECTOR
解析:
- SEA (Single Executable Applications) :
- 这是 Node.js 较新的功能(实验性或刚稳定),允许你把 Node.js 二进制文件和你的 JS 代码打包成一个单独的可执行文件(类似 pkg 或 deno compile)。
- #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION:如果编译 Node.js 时没有禁用 SEA 功能,就会执行里面的代码。
- sea::FixupArgsForSEA(argc, argv) :
- 如果是普通启动(node script.js),参数就是系统传递的参数。
- 如果是 SEA 启动(运行打包后的 myapp.exe),系统传递的 argv[0] 是你的程序名,后面可能没有 js 文件名。这个函数负责**"欺骗"或"修正"**传入的参数,让 Node.js 内核知道"哦,我要运行嵌入在二进制里的那个脚本,而不是去磁盘找文件"。
- std::tie(...) = ...:这是一个 C++ 语法,用于将 FixupArgsForSEA 返回的一对值(新的 argc 和 argv)批量赋值给当前的 argc 和 argv 变量。
- StartInternal :
- 这才是真正干活的地方。Start 只是个"前台接待",处理完特殊的参数修正后,把任务交给 StartInternal 去初始化 V8 引擎、加载 libuv 事件循环、运行 JS 代码等。
StartInternal 其他的看不懂不要紧 ,反正就知道这里是入口了
===>
python
static ExitCode StartInternal(int argc, char** argv) {
CHECK_GT(argc, 0);
// Hack around with the argv pointer. Used for process.title = "blah".
argv = uv_setup_args(argc, argv);
// uv_setup_args 是 libuv 的经典函数。
// 作用:在 Unix-like 系统上,进程的 argv 内存区域紧挨着环境变量(envp),修改 argv 可能覆盖环境变量导致崩溃。
// libuv 这里会重新分配一块内存,深拷贝 argv 字符串,并返回新指针。
// 这样后续 JS 中修改 process.title 时,就可以安全地在新内存里改,而不会破坏环境变量。
// Windows 上这个函数基本是 no-op(直接返回原 argv)。
std::shared_ptr<InitializationResultImpl> result =
InitializeOncePerProcessInternal(
std::vector<std::string>(argv, argv + argc));
// 这里调用 InitializeOncePerProcessInternal。
// 它的主要职责:
// 1. 解析 Node.js 自身的命令行参数(如 --inspect、--title、--experimental-sea 等)
// 2. 解析并分离 V8 参数(如 --harmony、--max-old-space-size 等,交给 V8 处理)
// 3. 处理特殊参数:--version、--help、--v8-options 等,会导致程序立即退出
// 4. 填充 per_process::cli_options(全局选项对象)
// 5. 返回一个 InitializationResultImpl,包含:
// - 处理后的 args 和 exec_args(传给脚本的参数)
// - 解析过程中遇到的错误信息
// - 是否需要 early return(比如 --version)
// - 最终退出码
// 如果解析参数时有错误(如非法选项),在这里打印出来
for (const std::string& error : result->errors()) {
FPrintF(stderr, "%s: %s\n", result->args().at(0).c_str(), error.c_str());
}
// 如果是 --version、--help、--v8-options 或其他只需打印信息就退出的选项
// early_return() 会返回 true,直接退出,不启动 V8 引擎
if (result->early_return()) {
return result->exit_code_enum();
}
DCHECK_EQ(result->exit_code_enum(), ExitCode::kNoFailure);
const SnapshotData* snapshot_data = nullptr;
// 保证函数退出时一定调用 TearDownOncePerProcess() 清理资源
auto cleanup_process = OnScopeLeave([&]() {
TearDownOncePerProcess();
if (snapshot_data != nullptr &&
snapshot_data->data_ownership == SnapshotData::DataOwnership::kOwned) {
delete snapshot_data;
}
});
uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);
// 处理 Single Executable Application (SEA) 功能
std::string sea_config = per_process::cli_options->experimental_sea_config;
if (!sea_config.empty()) {
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
return sea::BuildSingleExecutableBlob(
sea_config, result->args(), result->exec_args());
#else
fprintf(stderr, "Single executable application is disabled.\n");
return ExitCode::kGenericUserError;
#endif
}
// --build-snapshot:生成快照模式(用于打包成单可执行文件)
if (per_process::cli_options->per_isolate->build_snapshot) {
if (per_process::cli_options->per_isolate->build_snapshot_config.empty() &&
result->args().size() < 2) {
fprintf(stderr,
"--build-snapshot must be used with an entry point script.\n"
"Usage: node --build-snapshot /path/to/entry.js\n");
return ExitCode::kInvalidCommandLineArgument;
}
return GenerateAndWriteSnapshotData(&snapshot_data, result.get());
}
// 普通模式:尝试加载内置快照(blob)
if (!LoadSnapshotData(&snapshot_data)) {
return ExitCode::kStartupSnapshotFailure;
}
// 创建主 Node 实例,进入事件循环并执行用户脚本
NodeMainInstance main_instance(snapshot_data,
uv_default_loop(),
per_process::v8_platform.Platform(),
result->args(),
result->exec_args());
return main_instance.Run(); // 启动 REPL 或运行脚本,直到进程退出
}

三、kale
从第一个词看起
什么是shared_ptr
std::shared_ptr 是 C++ 标准库( 头文件)提供的智能指针之一,用于自动管理动态分配对象的生命周期,支持多个指针共享同一个对象的所有权。
- 共享所有权:多个 shared_ptr 可以指向同一个对象,当所有 shared_ptr 都销毁或重新指向其他对象时,才会自动删除底层对象。
- 引用计数:内部使用一个引用计数器(reference count)记录有多少个 shared_ptr 共享这个对象。
- 拷贝/赋值时:计数 +1
- 析构或 reset 时:计数 -1
- 计数降为 0 时:自动 delete 管理的对象
python
class Myclass{
public:
Myclass(int value):data(value) {
std::cout << "MyClass 构造: " << data << std::endl;
}
~Myclass(){
std::cout << "MyClass 销毁: " << data << std::endl;
}
void print() const {
std::cout << "值: " << data << std::endl;
}
void set(int value) {
data = value;
}
private:
int data;
};
int main() {
// ============ 彻底解决中文乱码神级代码(Windows 专用)===========
SetConsoleOutputCP(CP_UTF8); // 让 printf / cout 输出用 UTF-8
setvbuf(stdout, nullptr, _IOFBF, 4096);
std::shared_ptr<Myclass> sp1(new Myclass(11));
std::cout << "sp1 use_count: " << sp1.use_count() << std::endl; // 1
auto sp2 = std::make_shared<Myclass>(22);
auto sp3=sp2;
std::cout << "sp2 use_count: " << sp2.use_count() << std::endl; // 2
std::cout << "sp1 use_count: " << sp1.use_count() << std::endl; // 1
std::cout << "sp3 use_count: " << sp3.use_count() << std::endl; // 2
Myclass sp5 = Myclass(55);
sp5.print();
sp1->print();
sp2->print();
(*sp2).print();
经过一通查概念,其实就是类似python的浅拷贝 ,通过它赋值的新对象,实际上和它是一个引用。
(*sp2).print()
这里怎么怪怪的 , sp5.print(); 是实例对象方法,但是后面两种是什么鬼
| 写法 | 含义 | 等价于 | 使用场景 |
|---|---|---|---|
| sp1->print(); | shared_ptr 重载了 operator->,直接返回内部裸指针 | sp1.get()->print(); | 最常用、最推荐 |
| sp2->print(); | 同上,和 sp1 完全一样 | 同上 | 同上 |
| (*sp2).print(); | 先用 operator* 解引用得到 MyClass& 引用,再用 . 调用 | MyClass& obj = *sp2; obj.print(); | 想强调"得到对象本身"时 |
**简单理解 不是普通的箭头 ,而是 sp3-> (注意停顿) print(); **
*sp3-> 这个 是一个整体 ,等于 解引用,****(sp2) 就是它管理的对象 。
- shared_ptr 提供了一个成员函数叫 .get()。
- 返回值:返回底层管理的裸指针(raw pointer),类型是 MyClass*。
拿数组类比一下:
cpp
int arr[5] = {10, 20, 30, 40, 50};
std::cout << *arr << "\n"; // 输出 10,等价于 arr[0]
std::cout << *(arr + 1) << "\n"; // 输出 20,等价于 arr[1]
std::cout << arr[2] << "\n"; // 输出 30(最常用的下标方式)
好了下篇继续第二行
更多文章,敬请关注gzh:零基础爬虫第一天
