从C子集到MIPS汇编:手写编译器关键步骤与避坑指南
简介面向编译原理课程实验的完整 C-MIPS 编译器项目适合高校计算机专业学生完成词法分析、语法分析、静态语义分析、中间代码生成与优化、目标代码生成等环节最终可在 MARS 汇编器及自研 CPU 上验证运行。资源共 26 个文件压缩包约 1.26MB包含 Flex/Bison 文法文件l/y、C 源程序c/h、实验报告 PDF、README 说明文档以及多张运行截图与过程图片目录结构清晰便于对照复现。已有 533 人学习下载。内容涵盖 lex.l、parser.y、Analysis.c、ast.c、TargetCode.c 等关键模块DAG 优化与寄存器分配实现均有体现报告和截图可帮助理解抽象语法树打印、符号表构建及中间代码生成流程。对正在做编译原理实验、需要参考完整可运行方案或撰写课程报告的同学具有较高实用价值。1. 这个实验真正难的不是编译原理而是“收住”做“基于精简 C 语言的 C-MIPS 编译器”这门编译原理实验多数人以为难点在递归下降、FIRST/FOLLOW 集合或者语义动作做下来才发现真正卡人的是另一件事你写的 C 子集到底算什么以及生成的 MIPS 汇编怎么在模拟器里跑通。这个实验的本质是从一个裁剪过的 C 语言子集出发经过词法、语法、语义分析最终输出能在 MIPS 模拟器上运行的汇编代码。它适合两类人一类是正在上编译原理课、被实验报告追着跑的学生另一类是想把“编译器前端到后端”这条链路亲手捋一遍的开发者。如果你只想“抄一个能过验收的作业”这篇未必能直接给你完整源码但如果你想搞懂每一步为什么这么做、出问题时从哪查这篇会非常值。2. 先定 C 子集的边界再谈词法和语法别一上来就写 flex/bison2.1 精简 C 语言到底裁掉什么从语言子集反推编译器规模我见过不止一个组把“精简 C 语言”做成“完整 C 语言的未完成品”然后不停地给语言加特性结果前端写完已经期末了。这个实验的核心约束不是“你不能做完整 C”而是“你的编译器必须在限定时间内稳定输出可运行的 MIPS 汇编”。所以子集定多大直接决定你后面要填多少坑。我一般会把子集裁成下面这个样子语言成分我的选择理由全局变量支持 int 数组和 int 标量数组是后面字符串和指针的替代品MIPS 的.data段能直接映射局部变量只支持 int局部数组一旦出现栈帧偏移计算复杂度立刻上来了函数int f(int a, int b) 形式参数最多 4 个正好对应 MIPS 的$a0-$a3语句if/else、while、return、赋值、表达式语句覆盖结构化编程的基础表达力表达式算术四则、取负、括号、比较、逻辑与或非代码生成时统一翻译成栈式指令不支持的指针、结构体、switch、for、do-while、全局初始化列表、递归深度超过 20 的调用这些特性要么需要额外的类型系统要么会让栈帧管理变复杂这个子集不是拍脑袋定的。它让语法分析只需要考虑一个类型让符号表只需要记录“int/数组/函数/参数”让后端只需要处理 4 字节对齐的栈帧。你可以在完成一遍之后再往里面加 for、do-while 或者全局初始化但起步阶段别贪。2.2 手写词法分析器用状态机做最短实现符号表用线性表就够词法分析我强烈建议手写不是不能用 flex而是当前这个子集总共也就十几种 token用 flex 还要额外处理生成代码的环境配置问题。手写一个基于字符状态机的扫描器代码量不会超过 100 行而且调试的时候可以单步跟踪每个字符。一个可用的最小扫描器核心是这样的typedef enum { TOK_INT, TOK_IF, TOK_ELSE, TOK_WHILE, TOK_RETURN, TOK_ID, TOK_NUM, TOK_ASSIGN, TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE, TOK_SEMI, TOK_COMMA, TOK_EQ, TOK_NE, TOK_LT, TOK_GT, TOK_EOF } TokenKind; typedef struct { TokenKind kind; char text[256]; int line; } Token;扫描的时候用一个get_next_token()函数跳过空白然后按首字符分支isalpha开头就继续读字母数字下划线组成标识符isdigit开头就组成数字*要特别小心因为*后面如果跟着/那就是注释否则才是乘号。注释我建议只支持/* ... */不支持//因为//和除法符号的歧义会让初学者踩坑。符号表这一层常见的简单实现是数组加strcmp查找typedef struct { char name[256]; int type; // 1全局变量, 2局部变量, 3函数, 4参数 int offset; // 局部变量在栈帧中的偏移全局变量则是 .data 段的标签序号 int func_param_count; } Symbol; Symbol symtab[1024]; static int sym_count 0; int lookup_sym(const char *name) { for (int i 0; i sym_count; i) if (strcmp(symtab[i].name, name) 0) return i; return -1; }这里有个新手容易踩的点符号表必须在语法分析的过程中边声明边登记。你不能先扫描完所有 token 再统一登记因为「先使用后声明」在 C 子集里不应该被允许你要在声明语句的位置实时写入符号表后续表达式引用变量时直接查表找不到就报“未声明标识符”。用数组加线性查找确实慢但实验规模的代码量根本感知不到。2.3 递归下降解析建 AST 时顺便完成语义检查语法分析我默认用递归下降因为 C 语言子集的文法基本是 LL(1) 的写起来就是「每个非终结符一个函数」。这里最关键的设计是在语法分析阶段一边建 AST 一边做语义检查而不是等 AST 建完再单独跑一趟。AST 节点我用一个简单的结构体typedef enum { ND_INT_DECL, ND_ASSIGN, ND_EXPR_STMT, ND_RETURN, ND_IF, ND_WHILE, ND_BLOCK, ND_BINOP, ND_LVALUE, ND_RVALUE, ND_NUM } NodeKind; typedef struct Node Node; struct Node { NodeKind kind; int val; // ND_NUM 时用 char name[256]; // ND_LVALUE/ND_RVALUE 时用 Node *lhs, *rhs, *next; int line; // 每个节点记录行号报错时定位 };写表达式解析器是递归下降里最标准的套路——parse_expr调用parse_termparse_term调用parse_factorparse_factor处理数字、变量、括号。模板如下Node *parse_term(void) { Node *lhs parse_factor(); while (peek()-kind TOK_STAR || peek()-kind TOK_SLASH) { Token op advance(); Node *rhs parse_factor(); Node *nd new_node(ND_BINOP); nd-name[0] op.text[0]; nd-lhs lhs; nd-rhs rhs; nd-line op.line; lhs nd; } return lhs; }这段代码的核心逻辑就是“左结合”。while循环里不断消费乘号和除号每消费一次就生成一个新的ND_BINOP节点把之前的左操作数挂到新节点的lhs上。你如果写成if而不是while那1 * 2 * 3解析到第二个乘号就停了。这种错误出现时的现象不是编译报错而是生成的汇编默默少算了一部分极难排查。语义检查我建议在parse_decl和parse_assign里顺手做声明时先查重重复声明直接报错赋值时检查左侧是否确实是可声明的变量右侧表达式的类型是否是 int。因为整个子集只有一个类型这里的“语义检查”基本退化成符号存在性检查和赋值合法性检查。真正需要严格处理的是函数调用的参数个数匹配——调用的实参个数必须和声明时的形参个数一致这个在后端打包参数时会直接决定栈帧偏移。3. 从 AST 到 MIPS 汇编栈帧布局和控制流是后端的左膀右臂3.1 中间表示选型为什么 AST 直接遍历也可以很多教材会给你灌输「编译器架构必须要有中间表示 IR否则没法做优化」。这句话在高水平的编译器里是对的但在这个实验的规模下AST 本身就是你的 IR。你需要的只是在遍历 AST 时把表达式的计算过程翻译成“压栈-弹栈”模型把语句翻译成“标签-跳转”模型。什么时候你需要引入三地址码我个人的判据是当你发现函数调用参数求值顺序和栈帧偏移对不上或者表达式树太深导致 MIPS 的临时寄存器不够用的时候。这个实验的表达式深度通常不会超过 20 层$t0到$t9完全够用所以不需要额外做 IR。直接遍历 AST 的一个好处是你可以在同一个递归函数里同时完成“生成代码”和“维护栈帧信息”。你的gen_expr(Node*)函数每递归一次就能拿到当前节点的类型、值、变量名然后决定发射什么汇编指令。这比构造 IR 再遍历 IR 少了一半代码量。3.2 栈帧布局模板算清偏移局部变量才不会互相踩MIPS 的栈帧布局是后端最容易被低估的部分。常见的做法是每个函数入口统一执行三件事压栈保存$ra和$fp、把$sp赋给$fp、然后给所有局部变量一次性分配栈空间。我一般采用的模板如下func_foo: # 压栈保存返回地址和帧指针 addiu $sp, $sp, -8 sw $ra, 4($sp) sw $fp, 0($sp) move $fp, $sp # 为局部变量分配空间total_frame 在编译期算好 addiu $sp, $sp, -total_frame # 函数体代码 # 返回值放入 $v0 move $sp, $fp lw $ra, 4($sp) lw $fp, 0($sp) addiu $sp, $sp, 8 jr $ra这里最关键的是total_frame的计算。我建议在语法分析阶段每声明一个局部变量就给symtab里的该变量分配一个offset。这个offset从 0 开始每多一个 int 变量就减 4。为什么减因为栈是向下增长的第一个局部变量离$fp最近。访问局部变量时统一用$fp作为基址寄存器# 访问 offset 为 -12 的局部变量 lw $t0, -12($fp)把全局变量、局部变量、参数的偏移规则分别定义清楚是所有代码生成后面的地基。你一旦把「局部变量偏移从$fp往下算、参数从$fp往上算」这条规则定下来后端就变成机械翻译了。3.3 表达式代码生成栈式计算机模型与三句固定模板表达式生成统一采用“压栈求值”的模型。每个表达式求值完成后结果存放在栈顶。一个整数常量表达式的生成规则是固定的三条指令void gen_num(Node *n) { printf( li $t0, %d\n, n-val); printf( sw $t0, 0($sp)\n); printf( addiu $sp, $sp, -4\n); }逻辑很简单加载立即数到$t0存到当前栈顶栈指针向下移动 4 字节。二元运算的模板就是先递归生成左操作数再递归生成右操作数此时左操作数在栈的更深处右操作数在栈顶弹出两个数运算后再压回void gen_binop(Node *n) { gen_expr(n-lhs); gen_expr(n-rhs); // 栈顶是右值栈顶4 是左值 printf( lw $t1, 0($sp)\n); // 右值 printf( lw $t2, 4($sp)\n); // 左值 printf( addiu $sp, $sp, 4\n); // 收缩栈帧弹掉右值 switch (n-name[0]) { case : printf( addu $t0, $t2, $t1\n); break; case -: printf( subu $t0, $t2, $t1\n); break; case *: printf( mult $t2, $t1\n); printf( mflo $t0\n); break; case /: printf( div $t2, $t1\n); printf( mflo $t0\n); break; } printf( sw $t0, 0($sp)\n); printf( addiu $sp, $sp, -4\n); }这个实现的一个细节是弹出右操作数时只addiu $sp, $sp, 4弹出的值覆盖了左操作数的位置然后把运算结果写到新栈顶再压一次栈。这里如果搞反了$t1和$t2的顺序除法运算就会出现“除反”的问题运行时不会报错但结果错得莫名其妙。3.4 控制流生成if/else 与 while 的标签拼接控制流的生成要建立在“表达式生成器会把条件结果压栈”这个约定上。以if为例生成流程是先生成条件表达式然后弹出栈顶和零比较beq跳转到 else 标签条件不成立时执行else分支最后用j跳过整个if。static int label_seq 0; void gen_if(Node *n) { int L_else label_seq; int L_end label_seq; gen_expr(n-cond); printf( lw $t0, 0($sp)\n); printf( addiu $sp, $sp, 4\n); printf( beq $t0, $zero, L%d\n, L_else); gen_stmt(n-then); printf( j L%d\n, L_end); printf(L%d:\n, L_else); if (n-else_stmt) gen_stmt(n-else_stmt); printf(L%d:\n, L_end); }while 循环和 if 几乎一样区别只在最后要跳回条件判断的位置printf(L_begin:\n); printf(L_cond:\n); // 生成条件表达式并弹出比较 printf( beq $t0, $zero, L_end\n); // 生成循环体 printf( j L_cond\n); printf(L_end:\n);标签计数label_seq是全局唯一的避免嵌套 if/while 时标签重复。我见过有人用局部变量名作为标签后缀结果同一个函数里两个同名局部变量在不同作用域出现时标签直接冲突汇编器报“label defined twice”。统一用一个全局自增序号最保险。4. C-MIPS 编译器的 5 个高频坑从段错误到汇编不兼容4.1 词法阶段的“关键字误判”和“/**/ 注释吞字符”现象源码里写int解析出来却当成标识符或者1/*comment*/2解析完只剩一个1。原因词法扫描标识符时把if、int、while和普通变量名混在一起处理了没有在识别完整个标识符后做关键字比对。另一种常见情况是注释跳过逻辑只写了*的判断没有在*后面再判断/导致/*注释结束符识别失败。解决识别标识符时先把整个单词读进text然后做一次关键字表查找。注释处理严格按两个字符判断看到/后再看一眼下一个字符如果是*才进入注释跳过状态跳过过程中一旦看到*再看下一个字符是不是/是则结束。顺带一提我的实现里注释是不支持嵌套的/* /* */这种写法第二个/*不会被特殊处理如果实验报告里没有要求嵌套注释就别给自己增加难度。4.2 递归下降解析中的死循环不消费 token 的回车键现象编译某个输入文件时程序卡死CPU 跑满不报错也不退出。原因解析函数里有一个分支读到了某个 token但它不是预期的 token于是你调用advance()试图跳过但peek()拿到的是同一个 token或者空分支里根本没调用advance()解析器在同一条路径上反复重试。解决给自己定一条铁律——每个parse_*函数的每个分支都必须一次性消费至少一个 token。最简单的排查方式是在advance()里加一个计数器连续消费超过 10000 个 token 就把当前 token 行号打出来。另外一个隐藏坑是parse_expr里对-的处理取负号不能递归调用parse_expr必须递归调用parse_term否则-1 2会解析成-(1 2)。4.3 栈帧偏移算错局部变量互相覆盖函数调用后变量被改写现象一个简单的函数里声明了 3 个 int 变量运行时第一个变量赋值后第二个变量变成同样的值。原因符号表中局部变量的offset计算逻辑不对两个变量分到了同一个栈偏移。常见于语法分析声明变量时用了offset - 4但某个声明分支忘了减或者多个声明被合并到了一个offset计算里。解决在语法分析完成的瞬间打印一份符号表摘要做自检内容包含变量名、类型、offset。然后用一个只含 3 个局部变量和简单赋值的测试函数编译把生成的汇编打开手动检查每条访问局部变量的lw/sw指令的偏移是否互不相同。这个检查用脚本做最好直接 grep$fp附近的偏移量即可。4.4 printf 参数混乱MIPS 调用约定里的 $a0-$a3 与栈混用现象printf(%d, ab)输出的是一个无关数字或者模拟器直接报错。原因调用 printf 前把参数压进了栈但 printf 的光杆实现要么只从$a0取参数要么从错误的位置读参数。更隐蔽的是表达式ab的求值过程可能覆盖了$a0。解决把 printf 的调用约定简化为“只用第一个参数寄存器$a0传递格式化串后面的整数实参统一按顺序压栈”然后在你输出汇编代码时同步生成一个简化版printf函数它从栈里读取格式化串和实参逐个识别%d用 MARS 的系统调用li $v0, 1syscall打印整数。注意简化版 printf 内部不能调用你的普通代码生成机制否则$a0-$a3会被踩踏。4.5 生成的汇编在 MARS 能跑、换 SPIM 就不行伪指令差异现象你在 MARS 里运行一切正常交给老师后老师用另一种模拟器汇编报错。原因不同模拟器对伪指令的扩展规则不同。MARS 支持li $t0, large_constant自动拆成luiori但部分模拟器要求立即数不超过 16 位超限时报错。解决查阅目标模拟器的伪指令支持情况输出汇编时统一走保守策略大立即数用luiori组合加载不依赖li宏展开跳转标签全部用j而不是b加偏移。还有一个容易被忽略的差异是字符串字面量的存放方式.asciiz在大部分模拟器里都支持但如果你生成的是.ascii加上手动补零在某个模拟器上可能内存对齐有问题直接用.asciiz最省心。5. 把编译产物跑起来汇编器对接、printf 实现与测试驱动迭代5.1 生成 .s 文件后的第一轮自检标签、对齐和段布局你的编译器输出的 .s 文件不是直接丢给模拟器就能跑的。我建议在跑模拟器之前先做一轮静态自检人工检查三类内容.data和.text段的顺序是否正确、所有分支标签是否都有定义、函数入口处是否正确保存$ra。这一步用脚本化的方式做更靠谱比如用 grep 查找所有j L和beq ... L里的标签再提取冒号结尾的标签定义对比两边集合。MARS 里一个最小可用文件的结构通常是.data prompt: .asciiz result .text .globl main main: # 调用你的子函数 li $v0, 10 syscall注意.globl main必须有MARS 默认从main开始执行如果没有这一行你看到的将是模拟器直接跳到地址 0然后报错。5.2 把 printf 做成内建函数一组 syscall 完成所有格式输出你的 C 子集要不要支持printf我的建议是支持但只支持%d一种格式。实现方式不是链接外部库而是在编译器后端内置一个特殊的函数生成规则当遇到printf调用时往后端注册一个内建函数标签_printf同时按调用参数顺序把实参逐个求值后压栈然后调用_printf。_printf本身是你输出汇编时附带生成的一段固定代码_printf: # 从 $a0 读取格式化串, 从栈中读取实参 # 逐个字符扫描, 遇到 %d 就读取栈中的一个整数打印 # 遇到 \0 结束 li $v0, 10 syscall_printf里的参数顺序必须和调用侧压栈顺序严格对应。常见做法是实参从左到右求值从右到左压栈——也就是说最右的实参先压进栈底。这样_printf里按从栈顶到栈底的顺序依次弹出恰好是参数从右到左的顺序。这里我统一用“参数从左到右压栈_printf从栈底开始读”按一个方向定死不要混用。这个实现只能处理固定少量参数比如不超过 6 个但验证程序足够用了。5.3 用测试用例矩阵驱动迭代从算术到函数调用逐级放行我见过最有效的实验推进方式是维护一个从小到大的测试用例集每个用例只覆盖一个语言特性按顺序逐步放行。我建议至少准备下面这些档位阶段测试内容关键检查点1int main() { return 12; }返回值$v0是否为 32变量声明、赋值、取负符号表 offset 是否正确3if/else 分支条件跳转方向是否正确4while 循环累加求和标签循环是否正确闭合5单参数、多参数函数调用参数寄存器与栈帧恢复是否无误6递归函数求阶乘$ra和栈帧的保存/恢复是否成对每跑一个新用例如果出错先看生成的汇编而不是直接改编译器代码。汇编能直接看出是跳转标签错位、偏移算错、还是寄存器用错。测试驱动的另一个好处是你做实验报告的时候可以直接贴「用例-输出-汇编-说明」的对照比空谈后端设计有说服力得多。5.4 模拟器行为验证MARS 的 syscall 编号是硬编码的很多人在模拟器这一步纠结“为什么我生成汇编后 scanf 不能用”。MIPS 的syscall编号是模拟器定义的不是 MIPS 指令集自带的。MARS 里整数打印的调用方式是li $v0, 1、move $a0, 你要打印的值、syscall。读取整数则是li $v0, 5、syscall、结果在$v0。如果你在后端里实现了一个scanf的简化版就要按这套 syscall 编号来生成代码。这里有个细节MARS 的 syscall 会破坏$v0所以你的内建函数在使用$v0之前要先保存否则系统调用返回后原来放在$v0里的值就没了。我的习惯是所有内建函数里用到的临时寄存器统一在入口压栈用完再恢复这样调用者不需要知道内建函数内部用了哪些寄存器。6. 收尾技巧局部变量寄存器分配的“一步优化”与验证法如果你的实验进程顺利到后期你会明显感觉到全栈式代码生成虽然稳但很丑。每个局部变量读出来都先压栈再使用效率低到令人发指。这个时候可以先做一个小优化——把局部变量映射到 MIPS 的$t0到$t9寄存器上而不是全部放到栈里。做法是在语法分析阶段为每个局部变量分配reg_id从 0 到 9代码生成器引用这个变量时直接使用$t{reg_id}。当局部变量数量超过 10 个再把多出来的部分放入栈帧。这个优化能显著减少汇编行数也能让你在报告里写「实现了一个朴素的局部寄存器分配」。验证方法很简单拿同一段 C 代码分别关闭和开启寄存器分配对比两种汇编在同一个模拟器里运行的输出是否一致。如果一致说明分配没改坏语义如果不一致优先检查函数调用时有没有保存被调函数用到的$t寄存器——这是寄存器分配最常见的翻车点。我在这个实验里吃过最大的亏是只盯着 MARS 跑通就认为完成了换到老师指定的另一套模拟器后伪指令差异导致整个代码翻车。后来我把这套逻辑定成了自己的习惯编译器后端生成的汇编绝对不用模拟器特有的扩展伪指令遇见大立即数一律拆成luiori数据段一律用.data显式声明标签一律用纯数字前缀。这么做看着笨但可移植性极好也少了很多不可控的玄学问题。另一件我后期才想明白的事是所有代码生成函数必须能打印出“当前正在生成的源码行号”这样一旦生成的汇编在模拟器里地址越界你能根据行号定位到源码语句。没有这个机制你会在几百行汇编里硬找一条lw是哪个语句生成的血泪经验。希望这篇能帮你少走几段弯路把精力花在真正有意思的后端设计上。本文还有配套的精品资源点击获取

相关新闻

2026 跨境电商网站建设公司推荐-零售购物闭环与跨境体验的十家拆解

2026 跨境电商网站建设公司推荐-零售购物闭环与跨境体验的十家拆解

本期十家服务商先集中列出,便于横向对照:易百讯科技(深圳,2010 年成立,综合型建站服务商)、方维网络(深圳本土,建站与小程序并行)、助君网络(上海&#xff0c…

2026/10/12 11:43:42 阅读更多 →
rea:一个用Python写的环境审计命令行工具,几分钟看清老项目底细

rea:一个用Python写的环境审计命令行工具,几分钟看清老项目底细

第一次看到 rea 这个词,多半会以为是个拼写错误,或者某个框架名前缀。实际它是 Rapid Environment Auditor 的缩写,是我在维护一堆历史项目时被逼出来的命令行工具。起因很直接:接手一个老仓库,文档里的启动步骤早就失…

2026/10/12 11:43:42 阅读更多 →
Spring Boot @Value注入详解:配置外置、默认值与SpEL实战与踩坑

Spring Boot @Value注入详解:配置外置、默认值与SpEL实战与踩坑

做后端开发的兄弟应该都有过这种体验:项目里一堆配置散落在代码各个角落,数据库地址、文件上传路径、邮件开关、验证码过期时间,有些直接写死在代码里,有些放在配置文件里,到了切换环境的时候,打开代码全局…

2026/10/12 11:43:42 阅读更多 →

最新新闻

AI使用心得(二):用TaoToken统一Key打通Trae与Qwen的Spring Boot开发流

AI使用心得(二):用TaoToken统一Key打通Trae与Qwen的Spring Boot开发流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:37:01 阅读更多 →
医疗健康 AI Agent Harness Engineering:从辅助诊断到个性化治疗

医疗健康 AI Agent Harness Engineering:从辅助诊断到个性化治疗

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:37:01 阅读更多 →
MCP Server接入3D工作流:让AI真正读懂你的场景数据

MCP Server接入3D工作流:让AI真正读懂你的场景数据

1. 我的 3D 工作流里,为什么非要塞进一个 MCP server做 3D 项目做久了,你会发现一个特别拧巴的事:AI 语言模型再聪明,也看不懂我硬盘里的 .blend、.gltf、.fbx。它知道什么是"漫反射贴图",能跟你聊 PBR 材质…

2026/10/12 14:37:01 阅读更多 →
DeepSeek Harness:全插件化Agent运行时与可回放会话日志实战

DeepSeek Harness:全插件化Agent运行时与可回放会话日志实战

做了几年Agent应用,说实话踩过的坑比写出来的代码还多。最近我把自己常用的那套Agent运行骨架重新整理了一遍,抽出了一个内部代号叫 DeepSeek Harness 的轻量级运行时,核心就两件事:全插件化的装配机制,以及一套可回放…

2026/10/12 14:37:01 阅读更多 →
旋转圆盘上的摆动模拟:非惯性系下的动力学建模与数值实现

旋转圆盘上的摆动模拟:非惯性系下的动力学建模与数值实现

“旋转圆盘上的摆动模拟”听起来像个物理课建模题,但真正落地做的时候,你会发现它兼顾了刚体动力学、非惯性系分析和数值稳定性这几件麻烦事。我最早是因为一个同学在某个平台团建展示了一个演示:圆盘转起来,摆锤没有老老实实顺着…

2026/10/12 14:37:01 阅读更多 →
爬虫数据清洗四坑:编码乱码、时区错位、空值与脏字符实战

爬虫数据清洗四坑:编码乱码、时区错位、空值与脏字符实战

1. 第一个坑:编码乱码,不是你代码写错了,是requests猜错了先讲个最典型的场景。你刚装好环境,写了人生第一个爬虫:requests.get一个网页,然后print(response.text)。一运行,控制台吐出一堆“锟斤…

2026/10/12 14:36:00 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →