一、下载编译-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
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
.\vcbuild.bat- 编译成功后,可执行文件在:out\Release\node.exe
先贴一个git的链接 https://github.com/theanarkh/understand-nodejs/blob/master/docs/chapter01-Node.js%E7%BB%84%E6%88%90%E5%92%8C%E5%8E%9F%E7%90%86.md
二、基本路径
node/src/node_main.cc
intmain(intargc,char*argv[]){returnnode::Start(argc,argv);}#endif======>
libnode/src/node.cc
intStart(intargc,char**argv){#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATIONstd::tie(argc,argv)=sea::FixupArgsForSEA(argc,argv);#endifreturnstatic_cast<int>(StartInternal(argc,argv));}intStop(Environment*env,StopFlags::Flags flags){env->ExitEnv(flags);return0;}}//namespace node#if !HAVE_INSPECTORvoid 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 其他的看不懂不要紧 ,反正就知道这里是入口了
===>
static ExitCode StartInternal(intargc,char**argv){CHECK_GT(argc,0);//Hack aroundwiththe argv pointer.Usedforprocess.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(传给脚本的参数)//-解析过程中遇到的错误信息//-是否需要 earlyreturn(比如--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()){returnresult->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)returnsea::BuildSingleExecutableBlob(sea_config,result->args(),result->exec_args());#elsefprintf(stderr,"Single executable application is disabled.\n");returnExitCode::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");returnExitCode::kInvalidCommandLineArgument;}returnGenerateAndWriteSnapshotData(&snapshot_data,result.get());}//普通模式:尝试加载内置快照(blob)if(!LoadSnapshotData(&snapshot_data)){returnExitCode::kStartupSnapshotFailure;}//创建主 Node 实例,进入事件循环并执行用户脚本 NodeMainInstance main_instance(snapshot_data,uv_default_loop(),per_process::v8_platform.Platform(),result->args(),result->exec_args());returnmain_instance.Run();//启动 REPL 或运行脚本,直到进程退出}三、kale
从第一个词看起
什么是shared_ptr
std::shared_ptr 是 C++ 标准库( 头文件)提供的智能指针之一,用于自动管理动态分配对象的生命周期,支持多个指针共享同一个对象的所有权。
- 共享所有权:多个 shared_ptr 可以指向同一个对象,当所有 shared_ptr 都销毁或重新指向其他对象时,才会自动删除底层对象。
- 引用计数:内部使用一个引用计数器(reference count)记录有多少个 shared_ptr 共享这个对象。
- 拷贝/赋值时:计数 +1
- 析构或 reset 时:计数 -1
- 计数降为 0 时:自动 delete 管理的对象
classMyclass{public:Myclass(intvalue):data(value){std::cout<<"MyClass 构造: "<<data<<std::endl;}~Myclass(){std::cout<<"MyClass 销毁: "<<data<<std::endl;}voidprint()const{std::cout<<"值: "<<data<<std::endl;}voidset(intvalue){data=value;}private:intdata;};intmain(){//============彻底解决中文乱码神级代码(Windows 专用)===========SetConsoleOutputCP(CP_UTF8);//让 printf/cout 输出用 UTF-8setvbuf(stdout,nullptr,_IOFBF,4096);std::shared_ptr<Myclass>sp1(new Myclass(11));std::cout<<"sp1 use_count: "<<sp1.use_count()<<std::endl;//1auto sp2=std::make_shared<Myclass>(22);auto sp3=sp2;std::cout<<"sp2 use_count: "<<sp2.use_count()<<std::endl;//2std::cout<<"sp1 use_count: "<<sp1.use_count()<<std::endl;//1std::cout<<"sp3 use_count: "<<sp3.use_count()<<std::endl;//2Myclass 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*。
拿数组类比一下:
intarr[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:零基础爬虫第一天