帮我评估一下有没有可能windows弄成noCRT的,难度有多大即可,不用详细看完
● 根据我对 LuaJIT 源码的分析,总结如下:
可行性:理论上可行,但工作量极大 ┌───────────────────────────────────────────────────┬────────┬────────────────────────────────────────────────────────────────────────────────────────────┐ │ 模块 │ 难度 │ 说明 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ memcpy/memset/memmove/memcmp │ 容易 │ 换成 intrinsic 或自己写个循环即可 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ strlen/strcmp/strchr 等字符串函数 │ 中等 │ 调用点多但实现简单,就是体力活 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ malloc/realloc/free │ 容易 │ 只有 lib_aux.c 用到,包一层 HeapAlloc 就行 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ exit/abort/信号 │ 容易 │ 换成 ExitProcess │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ clock/time/localtime/setlocale │ 中等 │ 有 Win32 API 可替代 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ 文件 I/O │ 非常难 │ lib_io.c、lib_os.c、lib_package.c、luajit.c 四个文件大量依赖 stdio,需要全部重写为 │ │ (fopen/fread/fwrite/fprintf/fgets/tmpfile 等) │ │ CreateFile/ReadFile/WriteFile 等 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ 数学库 (pow/sin/cos/tan/log/exp/sqrt 等等) │ 非常难 │ lib_math.c + lj_vmmath.c 用到了全套数学函数,需要自己实现数值算法(或偷偷 link │ │ │ │ ucrtbase.dll) │ └───────────────────────────────────────────────────┴────────┴────────────────────────────────────────────────────────────────────────────────────────────┘
两个硬骨头
- 数学库 — x64 上 sqrt 可以用 SSE sqrtsd,但 pow、log、exp、sin、cos、tan、asin 等没有 CPU 指令,需要几千行的数值算法实现
- 文件 I/O — 这四个文件涉及所有 Lua 文件操作(读文件、写文件、管道、临时文件、错误输出),要全面改写 ┌───────────────────────────────────────────────────┬────────┬────────────────────────────────────────────────────────────────────────────────────────────┐ │ 模块 │ 难度 │ 说明 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ memcpy/memset/memmove/memcmp │ 容易 │ 换成 intrinsic 或自己写个循环即可 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ strlen/strcmp/strchr 等字符串函数 │ 中等 │ 调用点多但实现简单,就是体力活 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ malloc/realloc/free │ 容易 │ 只有 lib_aux.c 用到,包一层 HeapAlloc 就行 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ exit/abort/信号 │ 容易 │ 换成 ExitProcess │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ clock/time/localtime/setlocale │ 中等 │ 有 Win32 API 可替代 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ 文件 I/O │ 非常难 │ lib_io.c、lib_os.c、lib_package.c、luajit.c 四个文件大量依赖 stdio,需要全部重写为 │ │ (fopen/fread/fwrite/fprintf/fgets/tmpfile 等) │ │ CreateFile/ReadFile/WriteFile 等 │ ├───────────────────────────────────────────────────┼────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ 数学库 (pow/sin/cos/tan/log/exp/sqrt 等等) │ 非常难 │ lib_math.c + lj_vmmath.c 用到了全套数学函数,需要自己实现数值算法(或偷偷 link │ │ │ │ ucrtbase.dll) │ └───────────────────────────────────────────────────┴────────┴────────────────────────────────────────────────────────────────────────────────────────────┘
两个硬骨头
- 数学库 — x64 上 sqrt 可以用 SSE sqrtsd,但 pow、log、exp、sin、cos、tan、asin 等没有 CPU 指令,需要几千行的数值算法实现
- 文件 I/O — 这四个文件涉及所有 Lua 文件操作(读文件、写文件、管道、临时文件、错误输出),要全面改写
建议
你的 /MT 构建已经静态链接了 CRT,二进制不依赖外部的 msvcrt.dll,体积和兼容性已经大幅改善。如果非要彻底 noCRT,工作量相当于重写 LuaJIT 底层 IO 层 + 内嵌一个迷你数学库,而且后续上游升级维护成本极高。
更务实的方案:/MT + 链路裁剪(去掉不需要的 CRT 代码),基本能达到 “零 DLL 依赖” 的效果,而无需重写半个 LuaJIT。
要不要我详细说说 /MT 基础上还能做什么优化?
✻ Cogitated for 31s
❯ 我有点不太理解啊,我已经设置了MT模式,为什么生成的lib还是要连接CRT呢?MT模式只是针对exe吗
● 你理解得很对——这个问题很重要,我来解释清楚:
/MT 到底做了什么 /MT 和 /MD 的区别只在于 CRT 代码的来源位置:
- /MD(动态): .obj 里记录”去 MSVCRT.DLL 找这些函数”,exe/dll 启动时加载 MSVCRT.dll
- /MT(静态): .obj 里记录”去 libcmt.lib 找这些函数”,链接时链接器把 CRT 的机器码复制进 exe/dll
两种方式下,.obj 里引用的 CRT 符号完全一样——memcpy、printf、strlen 等等一个都没少。区别仅仅是这些函数的实现代码最终从哪里来。
那 .lib 是怎么回事?
一个 .lib(静态库)本质上是一个 .obj 文件的压缩包。里面的每一个 .obj 都包含:
memcpy → 未定义符号(需要外部提供) printf → 未定义符号(需要外部提供) strlen → 未定义符号(需要外部提供) …
.lib 本身不是最终的可执行文件,它不会链接任何东西。当你把 .lib 链接到你的项目时,链接器才会去解析这些符号。
所以你的 luajit_static.lib 并不是”还需要链接 CRT”——它里面躺着的 .obj 们只是记录着”我需要 memcpy、printf…”,不管用 /MD 还是 /MT,这些记录都在。
关键区别在执行文件层面
┌────────────┬───────────────────────────────────┬─────────────────────────────────────┐ │ 构建方式 │ EXE/DLL 运行需不需要 MSVCRT.dll? │ EXE/DLL 里有没有 CRT 代码? │ ├────────────┼───────────────────────────────────┼─────────────────────────────────────┤ │ /MD │ 需要(运行时加载 MSVCRT.dll) │ 有(但只加载不嵌入) │ ├────────────┼───────────────────────────────────┼─────────────────────────────────────┤ │ /MT │ 不需要 │ 有(嵌入在二进制里) │ ├────────────┼───────────────────────────────────┼─────────────────────────────────────┤ │ 真正 noCRT │ 不需要 │ 没有(但需要自己实现所有 CRT 函数) │ └────────────┴───────────────────────────────────┴─────────────────────────────────────┘
类比
- /MD:你说”我要一个锤子”,去邻居家借(MSVCRT.dll)
- /MT:你说”我要一个锤子”,去工具箱拿(libcmt.lib),放进口袋带走
- noCRT:你不需要锤子,但遇到钉子你得用手指按进去——也就是你得自己实现 pow、sin、fopen…
这就是我上一轮说的——/MT 已经解决了”零 DLL 依赖”的问题,但要真正”代码里没有 CRT 函数”(noCRT),需要重写大量代码。/MT 大概 80% 的好处(无外部依赖),20% 的工作量;true noCRT 剩下 20% 的好处,80% 的工作量。
问题一:/MT vs /MD 对静态库的影响
你的直觉错了。/MT vs /MD 对 .lib 绝对有影响。
区别在于 .obj 文件里记录的符号引用方式:
┌──────────┬───────────────────┬──────────────────────────────┐ │ 编译选项 │ printf 的调用方式 │ .obj 中的未解析符号 │ ├──────────┼───────────────────┼──────────────────────────────┤ │ /MT │ call printf │ printf(普通符号) │ ├──────────┼───────────────────┼──────────────────────────────┤ │ /MD │ call __imp_printf │ __imp_printf(DLL 导入符号) │ └──────────┴───────────────────┴──────────────────────────────┘
两者都是未解析的——静态库不解析符号,只是把 .obj 打包。区别在于最终链接时:
- /MT 的 printf → 链接器从 libcmt.lib(静态 CRT)找到实现 → 嵌入二进制
- /MD 的 __imp_printf → 链接器从 msvcrt.lib(导入库)找到 thunk → 指向 msvcrt.dll
你的 lua51.lib 是 /MT 编译的
build.zig 第 281-283 行的证据:
_ = msvc_stubs.add(“libLIBCMT.a”, ”!
/DEFAULTLIB
那么,Zig 的 no-CRT 努力白费了吗?
没有白费,但也不是完全成功。 让我追踪整个符号链:
lua51.lib (/MT 编译)
├─ 提供: luaL_newstate, vma_pcall, vma_close, …
├─ 未解析: malloc, free, printf, … ← CRT 函数
├─ 未解析: __security_cookie, … ← /GS 栈保护
└─ /DEFAULTLIB
↓ addObjectFilezafkiel-client.lib (link_libc = false, Windows) ├─ Zig 代码: HeapAlloc/HeapFree(无 CRT) ← ✓ 你的 no-CRT 成果 ├─ 提供: __security_cookie, __security_check_cookie, … ← Zig stubs ├─ 未提供: malloc, free, printf, … ← 这些仍悬空! └─ lua51.lib 的 .obj 原样传播,未解析符号带着走
↓ MSVC 链接器 (C DLL 项目)最终 C DLL ├─ __security_cookie → Zig stub 解决 ✓ ├─ malloc/free/printf → 谁来提供??? └─ 取决于 C DLL 项目的编译选项
关键在最后一步。 你的 Zig no-CRT 工作移除的是 Zig 自己产生的 CRT 引用(std.c.malloc → HeapAlloc),但 lua51.lib 的 CRT 引用原封不动地穿过了 Zig 静态库,落到最终 C DLL 的链接器头上。
最终 C DLL 的三个选择
选择 A:C DLL 用 /MT 编译
malloc/free/printf → libcmt.lib → 嵌入 DLL
- 不依赖外部 CRT DLL(CRT 代码在 DLL 体内)
- 增加 ~100KB 体积
- 这是”妥协版 no-CRT”——没有外部依赖,但 CRT 代码以静态方式存在
选择 B:C DLL 用 /MD 编译
malloc/free/printf → msvcrt.lib → 导入 vcruntime140.dll / ucrtbase.dll
- 启动就崩溃(LoadLibrary 时 Windows 加载器解析导入表失败,返回 ERROR_MOD_NOT_FOUND)
- 不会执行到任何代码,连 DllMain 都不进
选择 C:C DLL 用 /NODEFAULTLIB 且不链接任何 CRT
malloc/free/printf → 无提供者 → 链接器报错 unresolved external symbol
- 根本编译不过,链接阶段直接失败
实际情况
你现在能编译通过并运行,说明 C DLL 项目用了选择 A 或 B:
- 如果是 A:目标机器不需要 CRT DLL,但 CRT 静态代码在 DLL 体内 → 实质达成了”无外部 CRT 依赖”
- 如果是 B:目标机器需要 CRT DLL,但你的测试机恰好装了 VS Redist → 掩盖了依赖
你可以用这个命令验证最终 DLL 的 CRT 依赖:
dumpbin /imports your_final.dll | grep -i “vcruntime|msvcrt|ucrtbase”
如果输出为空 → 选择 A,真正无 CRT 依赖。如果有条目 → 选择 B,换上 /MT。
总结
- /MT vs /MD 对静态库绝对有影响——影响的是 .obj 里的符号命名(printf vs __imp_printf),而不是”嵌不嵌入代码”。
- Zig 侧的 no-CRT 工作没有白费——它确保了 Zig 代码本身不产生新的 CRT 依赖。但 lua51.lib 的 CRT 依赖是独立的问题,需要靠 C DLL 的 /MT 编译来消化。
- 如果你想要彻底零 CRT 代码(连静态嵌入都不要),需要重新编译 LuaJIT,替换掉它对 malloc/free/printf 的调用——就像你给 Zig 代码做的那样。LuaJIT 的 CRT 使用其实很有限(只有初始分配和错误输出),工作量不大。
编译lib MT/MD
编译lib的时候,MT/MD选择仍然是有意义的。