第 36 章 / 共 58 章 卷七 系统与互操作(收官)

标准库全景

卷七收官。前五章一直在讲"怎么和外部世界接线",本章回到你身边:515 个公开函数、115 个常量、33 个命名空间,全部默认注入编译环境,不需要一行 extern、不需要一行 import——math.sqrt 从第 1 章起就在手边。本章画出整张地图,把没来得及深讲的重点模块(math/random/hash/codec/encoding/log/json/xson/os/signal)逐一实测,最后点亮一个隐藏彩蛋:tcc 模块,在 xlang 脚本里直接编译运行 C。

本章导读

很多语言把"标准库很大"当成学习负担,xlang 反过来:标准库的用法你早就会了——第 5 章的 print、第 18 章的 string.split、第 20 章的 time.now(),全是标准库成员。本章要做的是三件事。一,讲透设计哲学:为什么不需要 import(默认注入),为什么 math 直通 CRT 而 time 走 XRT(XRT 优先原则),为什么没用到的函数不进你的可执行文件(按需注入,有 cgen 证据)。二,给全地图:33 个命名空间加全局 4 函数,每个多少函数、什么定位、哪章讲过,一张表查一辈子。三,补讲重点模块:math 的 34 个函数批量实测,random 的种子复现,hash 的 8 个变体,codec/encoding 的编解码对,log 的级别系统,json/xson 的往返,os/signal 的进程信息与信号处理,以及压轴的 tcc 模块——脚本里编译 C 源码跑出真 exe。读完你将获得一项元技能:会自己读标准库源码(36.10),从此不再依赖文档更新速度。

本章学习目标

理解标准库默认注入、短命名空间、XRT 优先、按需注入四条设计原则及其 cgen 证据;掌握 33 命名空间+全局的全景分布(515 函数/115 常量实测口径);会用 math 34 函数与 5 常量;会用 random(seed 复现/range/string/Random 类)与 hash 8 函数;会用 codec(base64/hex/URL 三对)与 encoding(detect/name/charSize/encodeUtf8/decodeUtf8);会用 log 级别系统与格式常量;会用 json/xson 的 parse/stringify/valid/read/write;会用 os 进程信息与 signal 信号注册/触发/计数;会用 tcc.run/tcc.compile 在脚本内编译运行 C;掌握 xl6_stdlib_data.c 的源码阅读法。

36.1 设计哲学

四条原则全部来自 docs/标准库设计.md,并且都能用编译器实测验证。

一,默认注入,免 import 免 extern。"DEMO6 标准库默认进入 xlang 编译环境,用户不需要为基础能力手写 extern 或 import。"与第 35 章的扩展库形成分工:标准库是"每行代码都可能用"的基础能力,默认可得;扩展库是按需引入的专业能力,import 显式声明。

二,XRT 优先。"能够由 XRT 提供的能力,必须优先映射到 XRT;确实属于 CRT 基础设施、没有必要在 XRT 重复实现的能力,可以直接映射到 CRT。典型例子是 math 命名空间。"所以 time/file/json 走 XRT(跨平台一致行为),math 走 CRT(sin/sqrt 没有重造轮子的理由)。

三,按需注入。编译器只为实际用到的标准库函数注入 extern 声明。实测一个只用了 math.sqrt 的程序,生成 C 里就一条声明、一次直调:

cgen 产物节选(实测)
double sqrt(double x);        // ← 唯一注入的数学声明
// 调用处:xvoCreateFloat((double)(sqrt(2.0)))

math.sqrt 展开后就是裸的 CRT sqrt——零包装、零开销,没用到的 500 多个函数连名字都不出现在产物里。

四,字符串所有权统一桥接。XRT 函数返回的需要 xrtFree 的堆字符串,由标准库薄封装统一调 take_cstr 复制进临时内存区后立即释放原指针——你拿到的永远是普通 string,"用户不需要也不能手动释放"(深入细节 2 展开)。

顺带一个反直觉事实:print 家族不经过 CRT 的 stdout——Windows 下生成代码直接用 WinAPI 标准输出句柄写入,POSIX 下用 write(1, ...),连 stdio.h 都不包含。这就是为什么 print 在混编场景永远没有缓冲刷新问题。

36.2 全景地图

本章对 src/stdlib/xl6_stdlib_data.c 注册表做了全量统计:515 个公开函数(唯一名)、115 个常量、33 个命名空间加全局。一表看全(函数数为唯一名计数,已讲章节给出回链):

命名空间函数常量定位回链
全局4—print/println/write/writeln 输出家族第 5 章
math345CRT 数学直通本章 36.3
string44—字符串全操作第 18 章
int / float1 + 1—数值格式化第 19 章
time67—时间与日期第 20 章
random5—随机数与随机字符串本章 36.4
hash8—非加密哈希(u32/u64/微纳秒级)本章 36.4
codec12—base64/hex/URL 编解码本章 36.5
encoding219字符集检测与 UTF 转换本章 36.5
log1610级别化日志(控制台/文件/滚动)本章 36.6
json73JSON 解析/生成/流式访问本章 36.7
xson83XSON(xlang 扩展记法)本章 36.7
dict1—dict.remove 键删除第 17 章
os10—平台/环境变量/参数/进程链本章 36.8
signal127信号注册/触发/路由本章 36.8
tcc75脚本内编译 C(内嵌 TCC)本章 36.9
xlang3—脚本内运行 xlang 脚本本章 36.9
path19—路径 join/abs/normalize 等stdlib.html
file3110文件读写/属性/原子写stdlib.html
dir23—目录遍历/glob/walkstdlib.html
process1111子进程 exec/pipeline/管道stdlib.html
crypto54—AES/SHA/RSA/ECC 等加密族stdlib.html
id5—XID 全局唯一标识stdlib.html
regex114正则编译/查找/替换stdlib.html
template5—模板引擎stdlib.html
task1—task.group 任务组第 31 章
net67本机 IP/MAC/代理解析卷九预告
tcp56TCP 连接/监听第 45 章
udp22UDP 绑定/收发第 46 章
url18—URL 解析/构造卷九预告
query10—查询串编解码卷九预告
http523HTTP 客户端第 43 章
http.server4810HTTP 服务器与路由第 44 章

读表三式:按任务查(要压缩去 codec,要日志去 log,要唯一 ID 去 id);按章节回(已讲章节有系统讲解,未讲的以 stdlib.html 参考页为准);按源码验证(36.10 的方法,任何函数的精确签名一分钟查清)。注意一个口径说明:功能清单文档写的还是"488 函数/105 常量"的旧数,本章的 515/115 是对注册表源码的实时统计——标准库在生长,以注册表为准(深入细节 3)。

36.3 math:CRT 直通

34 个函数分五族,全部批量实测通过。常量 5 个(pi/tau/e/nan/inf),在重写阶段直接替换为字面量,连函数调用都不发生:

族函数实测样例 → 结果
三角/双曲sin cos tan asin acos atan atan2math.deg(math.pi) → 180.0;math.rad(180.0) → 3.141592653589793
幂/根/对数sqrt cbrt pow exp exp2 expm1 log log2 log10 log1p hypotsqrt(2.0) → 1.4142135623730951;pow(2.0,10.0) → 1024.0;log(e) → 1.0;log10(1000.0) → 3.0;hypot(3.0,4.0) → 5.0
取整/舍入floor ceil round trunc fract modfloor(3.7) → 3.0;ceil(3.2) → 4.0;round(3.5) → 4.0;trunc(-3.7) → -3.0;mod(10.0,3.0) → 1.0
符号/最值/夹取abs min max clamp signabs(-4.2) → 4.2;clamp(15.0,0.0,10.0) → 10.0;sign(-8.0) → -1
判定isNaN isInf isFiniteisNaN(math.nan) → true;isFinite(42.0) → true

三个使用要点。一,参数返回统一 float(int 实参自动提升),唯 math.abs 有 int 重载保持整数。二,round 是编译器辅助实现(内部用 floor/ceil 组合),不是 CRT round——跨平台行为一致。三,nan/inf 是字面量常量,配合 isNaN/isInf 做防御:if math.isNaN(x) then ...。

36.4 random 与 hash

random 只有 5 个函数,但有两个关键词:seed(seed, seq) 双参数与可复现:

random 实测(输出注释为真实结果)
random.seed(uint64(2026), uint64(1))
print(random.u32())              // 831089821
random.seed(uint64(2026), uint64(1))   // 同种子同序列
print(random.u32())              // 831089821 —— 完全复现
print(random.range(1, 6))         // 1..6 闭区间整数
print(random.string(8))           // "1bQptd-0" 类随机串
print(random.string(6, "ABC"))    // 限定字符集,如 "BCAAAC"

同 seed 同 seq 给出完全相同的序列——测试与模拟场景的刚需;要不可预测就不调 seed(线程种子自动初始化)。需要多个独立流时用第 34 章见过的 Random native class:Random(uint64(42), uint64(1)) 每个实例一条流,官方示例验证过同参数两实例逐值相等。hash 的 8 个函数是非加密哈希(校验、分桶、指纹用):u32/u64 两宽度,Micro/Nano 两精度变体,WithSeed 三加盐版本:

hash 实测
print(int(hash.u32("hello")))                 // 1360413475
print(int(hash.u32("world")))                 // 1567078908 —— 输入变则哈希变
print(int(hash.u32WithSeed("hello", uint32(7))))  // 1956780885 —— 加盐变体

注意 int(...) 转换不能省——当前版本直接 print uint32/uint64 值会在链接期失败(坑 3,本章记录的编译器问题)。加密级哈希(SHA 家族)在 crypto 命名空间,别用 hash 做密码学用途。

36.5 codec 与 encoding

codec 是"字节↔文本"的三对编解码加一个百分号变体,全部成对实测:

codec 实测
print(codec.base64Encode("hello"))     // aGVsbG8=
print(codec.base64Decode("aGVsbG8="))  // hello —— 往返一致
print(codec.hexEncode("hi"))          // 6869
print(codec.hexDecode("6869"))        // hi
print(codec.formUrlEncode("a b+c"))   // a+b%2Bc —— 表单风格(空格变 +)
print(codec.percentEncode("中文"))     // %E4%B8%AD%E6%96%87 —— UTF-8 逐字节转义

每对都有 Bytes 变体(base64EncodeBytes 等)操作 Buffer。formUrl 与 percent 的差异就在空格:表单约定 +,纯百分号约定 %20。encoding 管更大的世界——字符集检测、命名、UTF 互转:

encoding 实测
print(encoding.isUtf8("中文"))             // true
int cs = encoding.detect("hello")
print(encoding.name(cs))                  // "utf-8"
print(encoding.charSize(cs))              // 1 —— 该字符集最小单元字节数
Buffer b = encoding.encodeUtf8("你好")
print(b.len())                           // 6 —— 两汉字各 3 字节
print(encoding.decodeUtf8(b))            // 你好 —— 往返一致

UTF-16/32 各有 LE/BE 的 encode/decode 四件套加字节序交换函数,9 个字符集常量(encoding.utf8/utf16/bom/autoDetect 等)配合 convertBytes 做任意字符集间转换。中文 Windows 上读老系统 GBK 文件时,convertBytes 就是你的翻译官。

36.6 log 日志系统

16 函数 + 10 常量构成完整日志系统:六个级别(trace/debug/info/warn/error/fatal)、三个输出目标(控制台/文件/滚动文件)、三种格式(text/simple/json)。默认开箱即用——带时间戳、线程号、颜色:

log.info("info msg")
2026-07-19 06:43:01 [INFO ] [tid=23340] [:0 ] info msg
log.warn("warn msg")
2026-07-19 06:43:01 [WARN ] [tid=23340] [:0 ] warn msg(黄色)

级别控制实测:默认级别是 info(trace/debug 被压掉),setLevel 动态调整,getLevel/levelName 可回读:

log 级别实战(实测)
log.setLevel(log.warnLevel)
log.info("you should not see this")   // 被级别压制,无输出
log.warn("warn after level set")      // 正常输出
print(log.levelName(log.getLevel())) // WARN

常量分两组:级别常量 traceLevel(0) 到 offLevel(6),格式常量 textFormat/simpleFormat/jsonFormat。生产用法是 create + addFile/addRollingFile 定制自己的 Logger 实例(滚动文件按大小切分,长期运行的服务必备),flush 强制落盘。log.write 是底层入口(自定义级别与格式串)。相比 print 调试法,log 给了你级别开关——上线后把 setLevel 提到 warn,调试输出一行不用删。

36.7 json 与 xson

json 7 函数覆盖解析/生成/校验/文件/流式,实测往返:

json 实测
var v = json.parse("{\"name\": \"xlang\", \"version\": 6, \"tags\": [\"a\", \"b\"]}")
print(v["name"])             // xlang —— parse 结果是动态容器,下标直取
print(v["version"])          // 6
print(json.valid("{bad json")) // false —— 先校验再解析的好习惯
print(json.stringify(v))     // {"name":"xlang","version":6,"tags":["a","b"]}

文件对 read/write 免手工 IO,stringify/write 的 pretty 参数控制美化;visit 是流式遍历大 JSON 的回调入口(配合 visitComplete/visitStopped/visitInvalid 三个常量判结果),writer 是增量构造器。与 derive json(第 25 章)的分工:derive 处理"已知类型的 class 实例序列化",json.parse/stringify 处理"结构未知或动态的 JSON 数据"——两者底层同一 XRT 引擎。xson 是 xlang 扩展记法,8 函数与 json 对应(多一个 options 构造 XsonOptions),list/set 这类 JSON 表达不了的容器走 xson:

xson 实测
var d = dict{"a": 1, "b": 2}
string text = xson.stringify(d)
print(text)                     // {"a":1,"b":2}(此例恰与 JSON 同形)
print(xson.valid(text))         // true
var back = xson.parse(text)
print(back["a"])               // 1 —— 往返一致

36.8 os 与 signal

os 回答"我在哪、我是谁":

os 实测(Windows 本机)
print(os.platform())                // "windows"
print(os.env("USERPROFILE") != "")  // true —— 环境变量读取
print(os.args().length >= 0)       // true —— 命令行参数数组

其余成员:cwd/setCwd 当前目录、exec/run/start 系执行外部程序(重型任务走 process 命名空间的管道族)、chain 串接命令。signal 是控制台信号的统一抽象,7 个常量覆盖跨平台交集:INT/TERM/HUP 是 Unix 老朋友,BREAK/CLOSE/LOGOFF/SHUTDOWN 是 Windows 控制台事件——同一套 API 两边跑。官方回归用例展示了完整四件套:回调注册(on/once)、channel 路由(sendTo)、任务组取消(cancelOn)、手工触发(raise):

signal 核心用法(官方 stdlib_signal.xl 提炼,实测通过)
int hits = 0
func(var) onInt = func(var event)
    hits = hits + int(event["code"])
end

SignalHandle direct = signal.on(signal.INT, onInt)
channel<var> events = channel::<var>(2)
SignalHandle routed = signal.sendTo(events, signal.INT)  // 信号进 channel

signal.raise(signal.INT)          // 手工触发(测试用)
// → 回调执行、channel 收到 {"code", "name"} 事件
signal.count(signal.INT)          // ≥ 1
signal.received(signal.INT)       // true
direct.close()                     // SignalHandle 用完即关
signal.restore(signal.INT)         // 恢复默认处理

实战意义:命令行工具用 signal.on(signal.INT, ...) 把 Ctrl+C 变成"优雅退出"(落盘、关连接);测试用 signal.raise 自触发验证处理逻辑;sendTo 把信号并入第 29 章的 channel 事件循环,cancelOn 把信号接到第 31 章的 TaskGroup 上做整组取消。

36.9 tcc:脚本内编译 C

本章彩蛋。xl.exe 内嵌了完整 TCC(第 1 章讲过 xlang 本身靠它编译),tcc 命名空间把这套能力直接开放给脚本——运行期编译 C 源码并执行:

tcc.run 实测(真实输出)
func main(...)
    string code = "#include <stdio.h>\nint main(void){ puts(\"hello from embedded C\"); return 42; }\n"
    int rc = tcc.run(code)
    println("rc =", rc)
end

// 输出:
// hello from embedded C      ← C 程序的 puts 直接打到控制台
// rc = 42                    ← C main 的返回值成为 tcc.run 的返回值

还能编译成真实可执行文件——tcc.compile(源码, 输出路径, 模式):

int rc2 = tcc.compile("int main(void){ return 0; }\n", "tcc_out.exe", tcc.EXE)
# compile rc = 0 —— 磁盘上多出 3 KB 的 tcc_out.exe,可独立运行

7 个函数分三组:run/runFile(内存编译执行)、compile/compileFile(产出文件,5 个模式常量 MEMORY/EXE/OBJ/DLL/PREPROCESS 可组合)、vfsMount/vfsClear(往编译用虚拟文件系统挂载动态生成的源码,配 runFile/compileFile 用)。Context 创建独立的 TccContext 实例做细粒度控制(加 include 路径、定义宏)。姊妹模块 xlang(3 函数:Context/run/runFile)同理运行 xlang 脚本——xlang 解释执行 xlang。应用场景:插件系统(用户提交 C 热代码)、教学演示(本书这种)、构建脚本里的代码生成器、运行时 JIT 式的热点下沉。安全提示是常识:tcc.run 执行的是任意 C,别编译不可信来源的代码。

36.10 自助阅读源码

标准库的全部真相在一个文件里:src/stdlib/xl6_stdlib_data.c(约 7700 行,随源码发布)。结构从上到下一目了然:

区段内容用法
类源码常量xl6StdlibRandomClassSource 等——Random/Buffer/Logger/File 等类的完整 xlang 源码看类的字段、方法、native 绑定(第 34 章的知识全用得上)
函数体字符串xl6StdJsonStringifyBody 等——每个公开函数的 xlang 薄封装源码看参数校验、默认值、底层调用哪个 __xl_std_/__xrt_ 桥函数
xl6StdlibConsts[]115 个常量表:命名空间、名字、类型、值查常量精确值(如 log.warnLevel = 3)
xl6StdlibFuncs[]914 条函数注册项:命名空间、公开名、内部名、C 符号、参数表、返回类型、函数体查任何函数的精确签名——比文档新、比文档准
计数宏头文件里 XL6_STDLIB_FUNC_COUNT / CONST_COUNT本章 515/115 的出处

查询套路:想知道 encoding.charSize 怎么用时,先 grep 注册表找到 {"encoding", "charSize", ...} 一行,记下函数体名 xl6StdEncodingCharSizeBody,再跳到函数体——发现它接 int charset 而非 string(坑 4 就是这么查清的)。914 条注册项 = 515 个唯一公开函数 + 重载 + 内部桥接层(__xrt_string/__xrt_time/__xl_runtime 等下划线命名空间不直接对你开放,是标准库自己的施工脚手架)。

深入细节

深入细节 1:三层结构与按需注入的实现

标准库调用在编译器里走三层:你写的 math.sqrt(2.0) → 语义检查前命中函数表、改写为内部名 → 注入 extern 声明(double sqrt(double x);)→ 走第 32 章的 C 混编 ABI 路径。设计文档原话:"这保证标准库和 C 混编使用同一条 ABI 路径"——所以标准库函数与你自己 extern 的 C 函数在性能、类型规则上毫无差别。按需注入的副产品:可执行文件体积与标准库总量无关,500 个没用的函数零成本。常量更彻底:math.pi 在重写阶段直接替换为字面量 3.14159265358979323846264338327950288,连符号都不存在。

深入细节 2:take_cstr——字符串所有权的隐形桥

XRT 很多函数返回"调用方负责 xrtFree"的堆字符串,xlang 的 string 却是非拥有的 const char* 视图——所有权模型天然冲突。标准库的统一解法:底层桥函数返回 point(原始指针),公开薄封装立即调 take_cstr——把字符串复制进 XRT 线程临时内存区,释放原指针,返回普通 string。临时内存随 xlUnit/xrtUnit 或线程退出自动回收,所以"用户不需要也不能手动释放"。这层桥也解释了为什么标准库函数密集调用不会漏内存:每次调用的临时串都在线程临时区登记在册,运行环境收尾时一笔勾销。自己写 extern 对接 XRT 风格 C 库时,可复制这个模式(第 32 章 string 边界的"返回即复制"原则与此同源)。

深入细节 3:515 与 488——活的标准库

功能清单文档写"公开函数 488 个、常量 105 个",本章实测注册表是 515/115。差值不是错误,是生长:write/writeln 进全局、http.server 补到 48、xson 增加 visit/writer、signal 补齐 Windows 控制台常量……文档的更新永远滞后于代码。这正是 36.10 教源码阅读法的原因:注册表是唯一实时口径,xl6StdlibFuncs[] 数组长度不会说谎。工程建议:升级 xl.exe 版本后,对注册表 diff 一下(两个版本的 xl6_stdlib_data.c 对比),新增函数一目了然——比翻变更日志可靠。

⚠️ 常见错误

⚠️ 坑 1:命名空间下写错函数名
    print(math.squrt(2.0))     // ← 报错(第 2 行):squrt 拼写
bad.xl:2:16: [stdlib] standard library namespace has no such symbol

原因:标准库命名空间只在成员访问位置生效,命中命名空间但符号不存在时给专属 [stdlib] 报错——与 undeclared identifier 区分开。正确写法:按 36.2 全景表或 36.10 的注册表查询法核对函数名。

⚠️ 坑 2:参数类型不匹配
    print(math.sqrt("hello"))   // ← 报错(第 2 行)
bad.xl:2:21: [sem] 函数调用实参类型和参数类型不匹配: target float, value string

原因:标准库函数同样走完整类型检查,math 族统一收 float。正确写法:数值传 float(int 自动提升);字符串先按第 7 章规则转换。

⚠️ 坑 3:直接 print uint32/uint64 值链接失败
    print(hash.u64("hello"))    // 编译过,链接炸:
tcc: error: unresolved reference to 'xl6_xrt_u64_to_string'

原因:本章实测确认的编译器/stdlib 缺口——print 对 uint32/uint64 的值格式化引用了未链接的 xl6_xrt_u64_to_string,random.u32()/hash.u32() 等返回值直接打印都会触发。正确写法:先 int(...) 转换再打印(36.4 实测可行);等官方修复后可去掉转换。

⚠️ 坑 4:凭直觉猜签名——encoding.charSize 接的不是字符串
    print(encoding.charSize("中"))   // ← 报错(第 2 行)
bad.xl:2:29: [sem] 函数调用实参类型和参数类型不匹配: target int, value string

原因:charSize 问的是"某字符集的最小单元字节数",参数是字符集代码 int(detect 的返回值),不是文本。正确写法:encoding.charSize(encoding.detect(text));签名存疑时用 36.10 的注册表查询法,别凭名字猜。

⚠️ 坑 5:tcc.run 的 C 源码里换行符层层转义
<string>:2: error: missing terminating " character
// tcc.run returned -1 —— C 编译失败

原因:C 源码走两遍转义——xlang 字符串一遍、C 字符串一遍。xlang 写法 \n 在 C 源码里变成真换行,若它落在 C 的字符串字面量内部,C 编译器就报未闭合。本章实测:要在 C 字符串里出现字面 \n,xlang 源码需写四个反斜杠。正确写法:优先用 puts(自带换行)避开内嵌 \n;必须内嵌时按四层转义写,或拆成多行拼接保持可读。

本章小结

知识点要点
规模口径515 公开函数(唯一名)+ 115 常量 + 33 命名空间与全局 4 函数;注册表为唯一实时口径
设计四原则默认注入免 import;XRT 优先 CRT 兜底;按需注入(cgen 取证);字符串所有权 take_cstr 桥
print 家族不经 CRT stdio:WinAPI 句柄 / write(1) 直写,无缓冲问题
math34 函数五族 + 5 常量;统一 float;round 为编译器辅助实现;常量重写期变字面量
random/hashseed(seed, seq) 双参可复现;Random 类多流;hash 非加密,uint 打印先 int(...)
codec/encodingbase64/hex/URL 三对成往返;detect→name→charSize 链;UTF-16/32 LE/BE 全套
log六级三目标三格式;默认 info 级;setLevel 动态开关;生产用 create+addRollingFile
json/xsonparse→动态容器下标取;valid 先校验;xson 承载 list/set 与 XsonOptions
os/signalplatform/env/args;signal 七常量跨平台,on/once/sendTo/cancelOn/raise 四件套
tccrun/runFile 内存执行、compile/compileFile 产真 exe、vfsMount 动态源码;5 模式常量;亮点:rc 即 C main 返回值
源码阅读法xl6_stdlib_data.c 五区段;914 注册项 = 515 公开 + 重载 + 内部桥;升级后 diff 注册表看新增

复习题

  1. 标准库为什么不需要 import?math.sqrt(2.0) 在生成的 C 里变成什么(提示:一条声明一次直调)?
  2. "XRT 优先"原则的含义是什么?math 为什么是 CRT 直通的典型例外?
  3. random.seed 需要几个参数?同参数两次 seed 后 u32 序列有什么性质?直接 print random.u32() 会遇到什么问题、怎么绕行?
  4. log 的默认级别是什么?setLevel(log.warnLevel) 之后 info/warn 各自的命运?
  5. tcc.run 与 tcc.compile 的区别是什么?tcc.run 的返回值来自哪里?

编程练习

练习 1:数据指纹工具

写一个脚本:读一段硬编码文本,输出它的 byteLen、base64 编码、hex 编码、u32/u64 双哈希(记得 int 转换)、以及 gzip 压缩后的长度(import 第 35 章的扩展库)。要求每个结果用 log.info 输出,最后用 setLevel(log.errorLevel) 验证全部静默。

参考思路:36.4/36.5/36.6 的实测片段直接组合;gzip.compress 接 string 重载;压缩长度用 Buffer.len()。

练习 2:随机数据生成器

用 random 生成 100 个 range(1, 100) 的整数放进 array,用 math 族的 min/max 和手写循环求均值,再用 json.stringify 把 {"count", "min", "max", "mean"} 四个字段输出。同一 seed 跑两次验证结果完全一致,然后去掉 seed 跑一次观察差异。

参考思路:均值用 int 累加后 / 100.0 转 float;json 的 dict 用 dict 字面量构造;复现性是 36.4 的核心卖点。

练习 3:脚本内 C 计算器

用 tcc.run 执行一段 C:从命令行硬编码两个整数做四则运算并 printf 结果(除法检查除零),C main 返回 0/1 表示成功失败;xlang 侧根据 rc 打印 "C side ok" 或 "C side failed"。进阶:把计算改成 tcc.compile 产出 calc.exe 并用 os.exec 运行它。

参考思路:printf 格式串里的 \n 用四层转义或改用 puts;os.exec 的用法查注册表 {"os", "exec", ...} 一行(36.10 方法实战)。