当前位置: 首页 > news >正文

简单程序语言理论与编译技术·22 实现一个从AST到RISCV的编译器

本文是记录专业课“程序语言理论与编译技术”的部分笔记。

LECTURE 22(实现一个从AST到RISCV的编译器)

一、问题分析

1、完整的编译器(如LLVM)需先完成AST到IR的转换,并进行代码优化,再到汇编,如下图:

本次实验实现从AST到RISC-V(不考虑优化)的翻译。对于汇编,我们可以访问Compiler Explorer (godbolt.org)来熟悉一下,或者参看类似下图的手册(包括汇编指令和寄存器类型等内容):

对于本次实验,我们用 x8 作为帧指针(fp) 来固定指向当前函数的栈桢基址,使用 a0 寄存器作为表达式计算结果的默认存放寄存器(函数返回值也约定放在 a0)。为了方便,我们避免寄存器分配,转而使用栈来保存局部变量和临时数据。如下图:

2、首先考虑Binop的翻译,我们参考下图:

对于Int 常数类型,我们直接将常数值加载到寄存器,对应RISC-V 提供的 li(load immediate)伪指令,将一个立即数载入寄存器。而对于Binop,我们考虑处理三种情况:
• Num Binop Num E.g. 2 + 7
• Num Binop Num Binop Num E.g. 2 + 6 * 9
• (Num Binop Num) Binop (Num Binop Num) E.g. (2 + 6) * (8 + 7)

基本流程:计算左操作数 -> 保存左值 -> 计算右操作数 -> 运算合成结果。如此之后,我们可以使用两种方案来保存:
• 将左值保存在临时寄存器中(例如t0 ),再计算右值到另一个寄存器(例如t1 )
• 采用栈,在计算左值后将其压栈保存,计算右值后再弹栈取出左值进行运算

3、然后考虑Let的翻译:

这里我们需要增加对变量和作用域的支持,使编译器可以处理let绑定和变量引用。我们需要考虑栈帧与变量地址,当进入一个新的let块,需在栈上分配新的空间保存局部变量的值(调整栈指针fp的offset)。然后在编译阶段也需要一个映射(符号表)跟踪当前作用域中每个变量名的栈偏移或寄存器位置(env)。

据此,我们简化逻辑,使fp固定不动,变量x偏移可以按照进入let的次序决定。而固定帧具体而言,使在程序一开始一次性分配最大所需栈,然后fp = sp固定。

我们可以考虑一个流程:先编译e1得到其值在a0,然后在栈上分配空间保存其值 (addi sp, sp, -8)。接着将变量名x映射到新空间的位置,例如offset += 8表示又用8字节,映射env新增 (“x”, of fset)。随后编译内部表达式e2,Var x按env找到偏移,完成后回退栈指针释放x所占的空间 (addi sp, sp, 8 )。最后恢复符号表,弹出env中x的绑定。

4、然后考虑If的翻译,我们参考下图:

我们需要扩展编译器支持Bool常量和If条件表达式,正确处理程序的控制流,为每个if表达式生成唯一的标签并正确安插跳转指令,且保证程序无论哪一种路径都会结束。

具体的,思考同前面let/变量的交互:使用标签而非固定地址。条件的各分支内部可以定义自己的变量且作用域仅限于分支内,实现需以递归的方式编译分支表达式并各自管理环境。在分支中寄存器值通过a0返回结果,无残留的临时变量,无需专门清理分支的栈。

5、最后考虑Func和App的翻译。扩展编译器支持函数定义(Func)和函数调用(App),函数的闭包包含函数代码和绑定环境的组合体。而编译Func(x, body)会产生两部分输出:全局函数代码(储存待后续统一输出),以及当前表达式计算时创建闭包的代码。

回顾Riscv寄存器,caller负责传递参数(a0-a7寄存器)和保存临时寄存器;callee可以自由使用临时寄存器但需保存和恢复caller保存寄存器(如s0等)。我们在实现时仅考虑函数调用一个显式参数,并需要注意传递参数值和环境指针。

闭包的环境结构(仅提供一种思路):分配一块内存,大小足够存放一个指针加上所有自由变量的值;将函数的代码地址写入这块内存的开头;将该函数的自由变量当前环境中的值写入内存的后续字段;将指向这块内存的指针作为闭包值放入某寄存器。

注意,Func需要一个唯一的新函数标签(类似if的标签)。对函数体进行的编译,与main形式上相同(env -> local_env + closure_env)。在main函数体换Func编译结果为上一步的闭包分配代码。

而App调用有多种方式:jalr、call等。以jalr为例:将a0(闭包指针)保存到t0,a0放参数;使用ld t1 , 0(t 0)加载闭包偏移为0处内容(代码地址)到t1;jalr ra, t 1进行跳转调用。

其余包括free、优化等问题不在本节内容之内。

二、前置代码

1、lib/ast.ml

type binop = | Add| Sub| Mul| Div| Leqtype expr =| Int of int| Var of string| Bool of bool| Binop of binop * expr * expr| Let of string * expr * expr| If of expr * expr * expr| Func of string * expr| App of expr * expr

2、lib/lexer.mll

{open Parser
}rule read = parse | [' ' '\t' '\n'] { read lexbuf }| '+' { PLUS }| '-' { MINUS }| '*' { TIMES }| '/' { DIV }| '(' { LPAREN }| ')' { RPAREN }| "<=" { LEQ }| "true" { TRUE }| "false" { FALSE }| "let" { LET }| "=" { EQUALS }| "in" { IN }| "if" { IF }| "then" { THEN }| "else" { ELSE }| "->" { ARROW }| "fun" { FUNC }| ['0'-'9']+ as num { INT (int_of_string num) }| ['a'-'z' 'A'-'Z']+ as id { ID id }| eof { EOF }| _ { failwith "Invalid character" }

3、lib/parser.mly

%{open Ast(** [make_apply e [e1; e2; ...]] makes the application  [e e1 e2 ...]).  Requires: the list argument is non-empty. *)
let rec make_apply e = function| [] -> failwith "precondition violated"| [e'] -> App (e, e')| h :: ((_ :: _) as t) -> make_apply (App (e, h)) t
%}%token <int> INT
%token <string> ID
%token PLUS MINUS TIMES DIV EOF
%token LPAREN RPAREN
%token LEQ
%token TRUE FALSE
%token LET EQUALS IN
%token IF THEN ELSE 
%token FUNC ARROW%nonassoc IN
%nonassoc ELSE
%left LEQ
%left PLUS MINUS
%left TIMES DIV%start main
%type <Ast.expr> main
%%main:expr EOF { $1 }
;expr:| simpl_expr { $1 }| simpl_expr simpl_expr+ { make_apply $1 $2 }
;simpl_expr:| INT { Int $1 }| ID { Var $1 }| TRUE { Bool true }| FALSE { Bool false}| simpl_expr LEQ simpl_expr   { Binop (Leq, $1, $3) }| simpl_expr TIMES simpl_expr { Binop (Mul, $1, $3) }| simpl_expr DIV simpl_expr   { Binop (Div, $1, $3) }| simpl_expr PLUS simpl_expr  { Binop (Add, $1, $3) }| simpl_expr MINUS simpl_expr { Binop (Sub, $1, $3) }| LET ID EQUALS simpl_expr IN simpl_expr  { Let ($2, $4, $6) }| FUNC ID ARROW expr { Func ($2, $4) }| IF simpl_expr THEN simpl_expr ELSE simpl_expr { If ($2, $4, $6) }| LPAREN expr RPAREN { $2 }
;

4、lib/dune

(library(name Simpl_riscv)(modules parser lexer ast))(ocamllex lexer)
(menhir (modules parser))

5、bin/dune

(executable(public_name Simpl_riscv)(name main)(modules main)(libraries Simpl_riscv)(flags (:standard -w -32-27-26-39-8-37)))

6、bin/main.ml的部分代码

open Simpl_riscv
open Astlet rec string_of_expr (e : expr) : string = match e with| Int n -> Printf.sprintf "Int %d" n| Var id -> Printf.sprintf "Var %s" id| Bool b -> let b_str = match b with | true -> "true"| false -> "false"inPrintf.sprintf "Bool %s" b_str| Binop (binop, e1, e2) ->let binop_str = match binop with | Add -> "Add"| Mul -> "Mul"| Sub -> "Sub"| Div -> "Div"| Leq -> "Leq"inPrintf.sprintf "Binop (%s, %s, %s)" binop_str (string_of_expr e1) (string_of_expr e2)| Let (var, e1, e2) -> Printf.sprintf "Let (%s, %s, %s)" var (string_of_expr e1) (string_of_expr e2)| If (e1, e2, e3) -> Printf.sprintf "If (%s, %s, %s)" (string_of_expr e1) (string_of_expr e2) (string_of_expr e3)| Func (var, e) -> Printf.sprintf "Func (%s, %s)" var (string_of_expr e)| App (e1, e2) -> Printf.sprintf "App (%s, %s)" (string_of_expr e1) (string_of_expr e2)let parse s : expr =let lexbuf = Lexing.from_string s inlet ast = Parser.main Lexer.read lexbuf inast(* 全局标签计数器,用于生成唯一标签 *)
let label_count = ref 0
let fresh_label prefix = incr label_count;Printf.sprintf "%s_%d" prefix !label_count(* 全局列表:保存所有生成的函数代码,最终附加在程序末尾 *)
let functions : string list ref = ref [](* 简单的自由变量分析(不去重,仅适用于教学示例) *)
let rec free_vars expr bound = match expr with| Int _ | Bool _ -> []| Var x -> if List.mem x bound then [] else [x]| Binop (_, e1, e2) -> free_vars e1 bound @ free_vars e2 bound| Let (x, e1, e2) -> free_vars e1 bound @ free_vars e2 (x :: bound)| If (cond, e_then, e_else) ->free_vars cond bound @ free_vars e_then bound @ free_vars e_else bound| Func (x, body) -> free_vars body (x :: bound)| App (e1, e2) -> free_vars e1 bound @ free_vars e2 bound(*compile_expr env cur_offset exprenv: (variable, offset) 的关联列表,其中 offset 是相对于 fp 的偏移(单位:字节)cur_offset: 当前已经分配的 let 变量字节数(每个变量占 8 字节)返回的汇编代码保证计算结果存放在寄存器 a0 中
*)
let rec compiler_expr (env : (string * int) list) (cur_offset : int) (expr : expr) : string = match expr with| Int n -> Printf.sprintf "\tli a0, %d\n" n| Bool b ->if b then "\tli a0, 1\n" else "\tli a0, 0\n"| Var x ->(trylet offset = List.assoc x env inPrintf.sprintf "\tld a0, -%d(fp)\n" offsetwith Not_found ->failwith ("Unbound variable: " ^ x))
(*——————————————*)and compile_expr_func (local_env : (string * int) list) (closure_env : (string * int) list) (cur_offset : int) (expr : expr) : string =match expr with
(*——————————————*)let compiler_program (e : expr) : string =let body_code = compiler_expr [] 0 e inlet prologue = ".text\n\.global main\n\main:\n\\taddi sp, sp, -64\n\\tmv fp, sp\n"inlet epilogue = "\\tmv sp, fp\n\\taddi sp, sp, 64\n\\tret\n"inlet func_code = String.concat "\n" !functions inprologue ^ body_code ^ epilogue ^ "\n" ^ func_codelet () =let filename = "test/simpl_test4.in" in(* let filename = "test/simpl_test2.in" in *)let in_channel = open_in filename inlet file_content = really_input_string in_channel (in_channel_length in_channel) inclose_in in_channel;(* let res = interp file_content inPrintf.printf "Result of interpreting %s:\n%s\n\n" filename res;let res = interp_big file_content inPrintf.printf "Result of interpreting %s with big-step model:\n%s\n\n" filename res; *)let ast = parse file_content in Printf.printf "AST: %s\n" (string_of_expr ast);let output_file = Sys.argv.(1) inlet oc = open_out output_file inlet asm_code = compiler_program ast inoutput_string oc asm_code;close_out oc;Printf.printf "Generated RISC-V code saved to: %s\n" output_file

三、具体实现

1、bin/main.ml的compiler_expr函数

let rec compiler_expr (env : (string * int) list) (cur_offset : int) (expr : expr) : string = match expr with| Int n -> Printf.sprintf "\tli a0, %d\n" n| Bool b ->if b then "\tli a0, 1\n" else "\tli a0, 0\n"| Var x ->(trylet offset = List.assoc x env inPrintf.sprintf "\tld a0, -%d(fp)\n" offsetwith Not_found ->failwith ("Unbound variable: " ^ x))| Binop (op, e1, e2) ->let code1 = compiler_expr env cur_offset e1 in let push_left = "\taddi sp, sp, -8\n\tsd a0, 0(sp)\n" inlet code2 = compiler_expr env cur_offset e2 inlet pop_left = "\tld t0, 0(sp)\n\taddi sp, sp, 8\n" inlet op_code = match op with| Add -> "\tadd a0, t0, a0\n"| Sub -> "\tsub a0, t0, a0\n"| Mul -> "\tmul a0, t0, a0\n"| Div -> "\tdiv a0, t0, a0\n"| Leq -> "Not implemented"incode1 ^ push_left ^ code2 ^ pop_left ^ op_code| Let (x, e1, e2) ->let code1 = compiler_expr env cur_offset e1 inlet new_offset = cur_offset + 8 inlet alloc = Printf.sprintf "\taddi sp, sp, -8\n\tsd a0, -%d(fp)\n" new_offset inlet env' = (x, new_offset) :: env inlet code2 = compiler_expr env' new_offset e2 inlet free = "\taddi sp, sp, 8\n" incode1 ^ alloc ^ code2 ^ free| If (cond, e_then, e_else) ->let label_else = fresh_label "Lelse" inlet label_end = fresh_label "Lend" inlet code_cond = compiler_expr env cur_offset cond inlet code_then = compiler_expr env cur_offset e_then inlet code_else = compiler_expr env cur_offset e_else incode_cond ^ Printf.sprintf "\tbeq a0, x0, %s\n" label_else ^code_then ^Printf.sprintf "\tj %s\n" label_end ^Printf.sprintf "%s:\n" label_else ^code_else ^Printf.sprintf "%s:\n" label_end| Func (x, body) ->let fvs = free_vars body [x] inlet num_free = List.length fvs inlet func_id = fresh_label "func" inlet local_env = [(x, 8)] inlet closure_env = List.mapi (fun i v -> (v, 8 * i)) fvs inlet func_body_code = compile_expr_func local_env closure_env 0 body inlet func_prologue = Printf.sprintf "%s:\n\taddi sp, sp, -16\n\tsd ra, 8(sp)\n\tsd fp, 0(sp)\n\tmv fp, sp\n" func_idinlet func_epilogue = "\tld ra, 8(sp)\n\tld fp, 0(sp)\n\taddi sp, sp, 16\n\tret\n"inlet func_code = func_prologue ^ func_body_code ^ func_epilogue infunctions := !functions @ [func_code];let closure_size = 8 * (1 + num_free) inlet alloc_code = Printf.sprintf "\tli a0, %d\n\tjal ra, malloc\n" closure_size inlet move_closure = "\tmv t0, a0\n" inlet store_code_ptr = Printf.sprintf "\tla t1, %s\n\tsd t1, 0(t0)\n" func_id inlet store_free_vars = List.mapi (fun i v ->let outer_offset =try List.assoc v env with Not_found -> failwith ("Unbound free var: " ^ v)inPrintf.sprintf "\tld t1, -%d(fp)\n\tsd t1, %d(t0)\n" outer_offset (8 * (i + 1))) fvs |> String.concat ""inlet ret_code = "\tmv a0, t0\n" inalloc_code ^ move_closure ^ store_code_ptr ^ store_free_vars ^ ret_code| App (e1, e2) ->let code_f = compiler_expr env cur_offset e1 inlet save_closure = "\tmv t0, a0\n" inlet code_arg = compiler_expr env cur_offset e2 inlet load_env = "\taddi a1, t0, 8\n" inlet load_code_ptr = "\tld t1, 0(t0)\n" inlet call = "\tjalr ra, 0(t1)\n" incode_f ^ save_closure ^ code_arg ^ load_env ^ load_code_ptr ^ call

2、bin/main.ml的compile_expr_func函数

and compile_expr_func (local_env : (string * int) list) (closure_env : (string * int) list) (cur_offset : int) (expr : expr) : string =match expr with| Int n -> Printf.sprintf "\tli a0, %d\n" n| Bool b ->if b then "\tli a0, 1\n" else "\tli a0, 0\n"| Var x ->if List.mem_assoc x local_env thenPrintf.sprintf "\tld a0, -%d(fp)\n" (List.assoc x local_env)else if List.mem_assoc x closure_env thenPrintf.sprintf "\tld a0, %d(a1)\n" (List.assoc x closure_env)elsefailwith ("Unbound variable in function: " ^ x)| Binop (op, e1, e2) ->let code1 = compile_expr_func local_env closure_env cur_offset e1 inlet push_left = "\taddi sp, sp, -8\n\tsd a0, 0(sp)\n" inlet code2 = compile_expr_func local_env closure_env cur_offset e2 inlet pop_left = "\tld t0, 0, 0(sp)\n\taddi sp, sp, 8\n" inlet op_code = match op with| Add -> "\tadd a0, t0, a0\n"| Sub -> "\tsub a0, t0, a0\n"| Mul -> "\tmul a0, t0, a0\n"| Div -> "\tdiv a0, t0, a0\n"| Leq -> "Not implemented"incode1 ^ push_left ^ code2 ^ pop_left ^ op_code| If _ -> failwith "Not implemented"| Func _ | App _ -> failwith "Nested functions not supported in function bodies" 

 

相关文章:

简单程序语言理论与编译技术·22 实现一个从AST到RISCV的编译器

本文是记录专业课“程序语言理论与编译技术”的部分笔记。 LECTURE 22&#xff08;实现一个从AST到RISCV的编译器&#xff09; 一、问题分析 1、完整的编译器&#xff08;如LLVM&#xff09;需先完成AST到IR的转换&#xff0c;并进行代码优化&#xff0c;再到汇编&#xff0…...

无锡无人机驾驶证培训费用

无锡无人机驾驶证培训费用&#xff0c;随着科技的迅速发展&#xff0c;无人机在众多行业中发挥着举足轻重的作用。从影视制作到农业监测&#xff0c;再到物流运输与城市规划&#xff0c;无人机的应用场景不断扩展&#xff0c;因此越来越多的人开始意识到学习无人机驾驶技能的重…...

[ctfshow web入门] web5

前置知识 引用博客&#xff1a;phps的利用 当服务器配置了 .phps 文件类型时&#xff0c;访问 .phps 文件会以语法高亮的形式直接显示 PHP 源代码&#xff0c;而不是执行它。.phps被作为辅助开发者的一种功能&#xff0c;开发者可以通过网站上访问xxx.phps直接获取高亮源代码 …...

第五章:架构安全性_《凤凰架构:构建可靠的大型分布式系统》

第五章 架构安全性 一、认证机制 核心知识点&#xff1a; 认证标准&#xff1a; HTTP Basic认证&#xff1a;Base64编码传输凭证&#xff0c;需配合HTTPS使用OAuth 2.0&#xff1a;授权框架&#xff0c;重点掌握四种授权模式&#xff1a; 授权码模式&#xff08;最安全&#…...

控件主题效果添加程序设计

以下是针对Qt Designer设计的控件添加阴影效果的完整解决方案&#xff0c;结合可视化设置与动态主题支持&#xff1a; 一、基础阴影效果实现方案 1. 通过QSS实现简易阴影&#xff08;适用于简单需求&#xff09; /* 使用多重边框模拟阴影效果 */ QFrame#customWidget {borde…...

用swift playground写个ios应用和大模型或者网站交互

import SwiftUIstruct ContentView: View {State private var textFieldText: String ""State private var outputText: String "输出将会显示在这里"private let tip:String "消息已发送&#xff0c;请等待"State private var history:[Stri…...

Mlivus Cloud SDK v2的革新:从痛点剖析到实战优化

目录 从V1到V2:开发者体验的范式转变 深度解析SDK v2的架构革新 1. 统一接口范式:终结API混乱时代 2. 原生异步支持:高并发场景的性能救星 3. Schema Cache机制:性能优化的隐形冠军 4. 全功能REST API:简化集成的关键 实战指南:从迁移到深度优化 平滑迁移策略 性…...

【图像处理基石】什么是AWB?

1. AWB&#xff08;自动白平衡&#xff09;的定义 AWB&#xff08;Auto White Balance&#xff09;是一种图像处理技术&#xff0c;通过算法校正不同色温光源下图像的色彩偏差&#xff0c;使白色在任何光照条件下都能准确呈现为白色&#xff0c;从而让图像颜色更接近人眼真实感…...

[蓝桥杯 2017 省 B] k 倍区间

P8649 [蓝桥杯 2017 省 B] k 倍区间 题目描述 给定一个长度为 N N N 的数列&#xff0c; A 1 , A 2 , ⋯ A N A_1,A_2, \cdots A_N A1​,A2​,⋯AN​&#xff0c;如果其中一段连续的子序列 A i , A i 1 , ⋯ A j ( i ≤ j ) A_i,A_{i1}, \cdots A_j(i \le j) Ai​,Ai1​,⋯…...

基于SSM的高校宿舍水电管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

【LLM系列】1.大模型简介

1. 基础 1.1 如何权衡模型的复杂度和性能&#xff1f; ├── a. 模型架构选择 │ ├── 简化架构 │ │ └── 选择较小的网络层数和宽度&#xff0c;降低复杂度&#xff1b; │ │ 可使用高性能基础模型如 Transformers 作为起点&#xff0c;根据需求缩放模型。 │ └──…...

从概念和设计哲学的角度详细解析工厂模式

从概念和设计哲学的角度详细解析工厂模式。 1. 工厂模式的核心目标 解耦&#xff1a;将对象的创建过程与使用过程分离&#xff0c;使用者无需关心对象如何被创建。 统一入口&#xff1a;通过一个接口或方法控制对象的生成&#xff0c;隐藏底层实现细节。 类比现实中的工厂&am…...

AI小白:机器学习VS深度学习

1 特征工程的范式革命 传统机器学习&#xff1a;手工特征工程的艺术 在传统机器学习中&#xff0c;特征工程是一个关键步骤&#xff0c;它涉及将原始数据转化为能够被机器学习模型高效利用的特征。这通常需要领域专家的经验和知识&#xff0c;以手动设计和提取特征。 例如&a…...

对应列表数据的分割和分组

要基于指定的流派列表分割数据&#xff0c;可以使用 布尔索引 或 groupby 结合筛选。以下是具体方法&#xff1a; 场景假设 数据列 genres 中的值可能是多流派的字符串&#xff0c;例如 "drama,action" 或 ["drama", "action"]。目标&#xff1…...

信息物理系统(CPS):中国 AI(DEEPSEEK)的未来路径

一、引言 人工智能&#xff08;AI&#xff09;的发展正从通用模型向垂直领域渗透&#xff0c;而信息物理系统&#xff08;CPS&#xff09;作为连接数字世界与物理世界的桥梁&#xff0c;为 AI 提供了新的发展方向。中国 AI 企业如 DEEPSEEK 通过开源策略和本土化优势&#xff…...

SEO长尾词优化实战技巧

内容概要 长尾关键词作为SEO策略的重要组成部分&#xff0c;能够有效捕捉细分领域的精准流量&#xff0c;降低竞争成本的同时提升转化效率。本文系统梳理了从关键词挖掘到流量转化的全链路优化方法&#xff0c;重点解析工具使用、布局策略及搜索意图匹配三大核心模块。通过结合…...

爬虫自动化工具:DrissionPage

1. DrissionPage初始 官网地址&#xff1a;&#x1f6f0;️ 概述 | DrissionPage官网 在当今互联网高速发展的时代&#xff0c;网页数据的获取和处理变得愈发重要。传统的自动化工具如 Selenium 在某些方面逐渐显露出一些局限性&#xff0c;而 DrissionPage 正是在这样的背景下…...

扩展库Scrapy:Python网络爬虫的利器

目录 一、扩展机制的核心原理 二、六大实用扩展库详解 1. 动态渲染神器&#xff1a;scrapy-playwright 2. 分布式架构&#xff1a;scrapy-redis 3. 反反爬利器&#xff1a;scrapy-zyte-smartproxy 4. 智能调度&#xff1a;scrapy-thunder 5. 数据管道&#xff1a;scrapy…...

L3-21

exer01 Message # 1.定义Message消息类和cmd,content,sender,to四个属性&#xff0c;其中to默认为None class Message:def __init__(self, cmd, content, sender, toNone):self.cmd cmdself.content contentself.sender senderself.to to # 2. 创建登录消息对象msg1,聊天消…...

04.游戏开发-unity编辑器详细-工具栏、菜单栏、工作识图详解

04.游戏开发&#xff0c;unity编辑器详细-工具栏、菜单栏、工作识图详解 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是Python基础语法。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性&#xff0c;希…...

GRBL运动控制算法(二)圆弧插补

前言 GRBL 是一款高性能、开源的嵌入式 CNC&#xff08;计算机数控&#xff09;控制器固件&#xff0c;专为 Arduino 平台优化&#xff0c;广泛应用于雕刻机、激光切割机、3D 打印机及其他精密运动控制场景。自 2009 年发布以来&#xff0c;GRBL 凭借其高效的运动规划算法、稳…...

《P1072 [NOIP 2009 提高组] Hankson 的趣味题》

题目描述 Hanks 博士是 BT&#xff08;Bio-Tech&#xff0c;生物技术) 领域的知名专家&#xff0c;他的儿子名叫 Hankson。现在&#xff0c;刚刚放学回家的 Hankson 正在思考一个有趣的问题。 今天在课堂上&#xff0c;老师讲解了如何求两个正整数 c1​ 和 c2​ 的最大公约数…...

矩阵分解中的梯度下降:详细实现方案(包含数学推导、代码实现和优化技巧)

矩阵分解中的梯度下降:详细实现方案(包含数学推导、代码实现和优化技巧) 矩阵分解是机器学习和数据科学中重要的技术,广泛应用于推荐系统、自然语言处理、图像处理等领域。梯度下降作为一种优化算法,在矩阵分解中常用于最小化目标函数以找到最佳的矩阵近似。本指南将详细…...

STM32F103C8T6实现 SG90 360 °电机转动

简介 基于上一篇 STM32F103C8T6实现 SG90 180 电机任意角度转动 本来想实现角度转动, 但靠舵机本身无法实现限位, 需要记录位置, 并且根据转速计算大概位置, 存在误差&#xff0c; 不实现角度转动了, 只实现正反转 代码 正向速度0.75为最大速度, 反向2.25&#xff0c; 接近1.5…...

RTDETR融合[CVPR2025]DnLUT中的MuLUTUnit模块

RT-DETR使用教程&#xff1a; RT-DETR使用教程 RT-DETR改进汇总贴&#xff1a;RT-DETR更新汇总贴 《DnLUT: Ultra-Efficient Color Image Denoising via Channel-Aware Lookup Tables》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/pdf/2503.15931 代码链接&#xf…...

大数据Spark(五十七):Spark运行架构与MapReduce区别

文章目录 Spark运行架构与MapReduce区别 一、Spark运行架构 二、Spark与MapReduce区别 Spark运行架构与MapReduce区别 一、Spark运行架构 Master:Spark集群中资源管理主节点&#xff0c;负责管理Worker节点。Worker:Spark集群中资源管理的从节点&#xff0c;负责任务的运行…...

二:python基础(黑马)

一&#xff1a;了解 1.1: python特点 python是完全面向对象的语言 函数&#xff0c;模块&#xff0c;数字&#xff0c;字符串都是对象&#xff0c;在python中一切皆对象 完全支持继承&#xff0c;重载&#xff0c;多重继承 支持重载运算符&#xff0c;也支持泛型设计 py…...

【马拉车 KMP 差分数组】P6216 回文匹配|省选-

本文涉及知识点 较难理解的字符串查找算法KMP C差分数组 马拉车算法 P6216 回文匹配 题目描述 对于一对字符串 ( s 1 , s 2 ) (s_1,s_2) (s1​,s2​)&#xff0c;若 s 1 s_1 s1​ 的长度为奇数的子串 ( l , r ) (l,r) (l,r) 满足 ( l , r ) (l,r) (l,r) 是回文的&#…...

C/C++测试框架googletest使用示例

文章目录 文档编译安装示例参考文章 文档 https://github.com/google/googletest https://google.github.io/googletest/ 编译安装 googletest是cmake项目&#xff0c;可以用cmake指令编译 cmake -B build && cmake --build build将编译产物lib和include 两个文件夹…...

提高MCU的效率方法

要提高MCU(微控制器单元)的编程效率,需要从硬件特性、代码优化、算法选择、资源管理等多方面入手。以下是一些关键策略: 1. 硬件相关优化 时钟与频率: 根据需求选择合适的时钟源(内部/外部振荡器),避免过高的时钟频率导致功耗浪费。关闭未使用的外设时钟(如定时器、UA…...

Ansible 实战:Roles,运维的 “魔法函数”

一、介绍 你现在已经学过tasks和handlers&#xff0c;那么&#xff0c;最好的playbook组织方式是什么呢&#xff1f;答案很简单&#xff1a;使用roles&#xff01;roles基于一种已知的文件结构&#xff0c;能够自动加载特定的vars_files、tasks以及handlers。通过roles对内容进…...

GO简单开发grpc

什么是grpc 首先我们需要了解&#xff0c;什么是grpc gRPC&#xff08;全称&#xff1a;google remote procedure call&#xff09;是由Google开发的一个高性能、开源的远程过程调用&#xff08;RPC&#xff09;框架。它基于 HTTP/2 协议&#xff0c;并且使用 Protocol Buffer…...

强引用,弱引用,软引用,虚引用,自旋锁,读写锁

强引用&#xff1a;强引用GC不会回收 软引用&#xff1a;内存够的话不回收&#xff0c;内存不够的话回收 弱引用&#xff1a;不管内存够不够&#xff0c;只要有GC就回收 虚引用&#xff1a;点get是null&#xff0c;但是GC后他会把引用放到引用队列里边 自旋锁&#xff1a;是指尝…...

C++异常处理 throw try catch

C 异常处理概述 C 异常处理机制提供了一种在程序运行时捕获错误或异常情况的方式。异常处理的目的是使得程序在遇到错误时能够优雅地终止或恢复&#xff0c;并防止程序出现崩溃。C 使用 try, throw, 和 catch 关键字来实现异常处理。 异常处理的基本结构&#xff1a; throw: …...

优化 Web 性能:管理第三方资源(Third-Party Summary)

在现代 Web 开发中&#xff0c;第三方资源&#xff08;如分析工具、广告脚本、字体服务&#xff09;为网站提供了丰富的功能&#xff0c;但也可能成为性能瓶颈。Google 的 Lighthouse 工具在性能审计中提供了“第三方资源概要”&#xff08;Third-Party Summary&#xff09;&am…...

第六章、 系统级 I/O

真题考点 考点一&#xff1a;Unix I/O 所有的 I/O 设备(例如网络、磁盘和终端)都被模型化为文件&#xff0c;而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式&#xff0c;允许 Linux 内核引出一个简单、低级的应用接口&#xff0c;称为…...

Jetpack Compose 自定义标题栏终极指南:从基础到高级实战

Jetpack Compose 自定义标题栏终极指南&#xff1a;从基础到高级实战 本文将带你彻底掌握 Compose 标题栏开发&#xff0c;涵盖 5 种专业级实现方案 性能优化技巧 完整可运行代码。 &#x1f4da; 核心方案对比 方案特点适用场景复杂度基础Row布局完全自定义&#xff0c;灵…...

晶晨S905-S905L-S905LB_S905M2通刷_安卓6.0.1_16S极速开机_线刷固件包

晶晨S905-S905L-S905LB_S905M2通刷_安卓6.0.1_16S极速开机_线刷固件包 线刷方法&#xff1a;&#xff08;新手参考借鉴一下&#xff09; 刷机工具版本请用2.2.0以上&#xff0c;导入固件后&#xff0c;刷机工具右侧两个擦除打勾&#xff0c;然后点开始。插上刷机神器&#xf…...

tkiner模块的初步学习

文章目录 一、前言二、概念2.1 安装2.2 窗口 三、小部件3.1 概述3.2 常用小部件3.2.1 Label3.2.2 Button3.2.3 Entry3.2.4 Text3.2.5 Listbox3.2.6 Checkbutton3.2.7 Radiobutton3.2.8 Scrollbar 3.3 更多小部件3.3.1 Scale3.3.2 Spinbox3.3.3. Progressbar 3.4 主题小部件 四、…...

Java常用数据结构操作方法全面总结

目录 一、List接口及其实现类二、Set接口及其实现类三、Map接口及其实现类四、Queue/Deque队列五、Stack栈六、树形结构七、注意事项与最佳实践总结 一、List接口及其实现类 核心实现类 ArrayList&#xff1a;基于动态数组LinkedList&#xff1a;基于双向链表 常用操作方法…...

Java的Selenium的特殊元素操作与定位之select下拉框

如果页面元素是一个下拉框&#xff0c;我们可以将此web元素封装为Select对象 Select selectnew Select(WebElement element); Select对象常用api select.getOptions();//获取所有选项select.selectBylndex(index);//根据索引选中对应的元素select.selectByValue(value);//选…...

STM32单片机入门学习——第15节: [6-3] TIM输出比较

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.05 STM32开发板学习——第15节: [6-3] TIM输出比较 前言开发板说明引用解答和科普一…...

力扣经典算法篇-9-跳跃游戏(贪心算法,反向递推)

题干&#xff1a; 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 …...

java面向对象 - 封装、继承和多态

1.封装 定义 封装是把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。通过访问控制修饰符(如private、protected、public)对属性和方法的访问进行限制,以此提升代码的安全性与可维护性。 要点 访问控制:运用private修饰属性,防止外部直…...

铁电液晶(FLC)与反铁电液晶(AFLC)

### **铁电液晶&#xff08;FLC&#xff09;与反铁电液晶&#xff08;AFLC&#xff09;的原理、区别及应用** --- ## **1. 基本原理** ### **&#xff08;1&#xff09;铁电液晶&#xff08;Ferroelectric Liquid Crystal, FLC&#xff09;** - **分子结构**&#xff1a; …...

经典算法 a^b

原题目链接 问题描述 求 a 的 b 次方对 p 取模的值&#xff0c;即计算&#xff1a; a^b mod p输入格式 输入一行&#xff0c;包含三个整数 a、b 和 p&#xff0c;中间用空格隔开。 输出格式 输出一个整数&#xff0c;表示 a^b mod p 的值。 数据范围 0 ≤ a, b ≤ 10^91 …...

Python解决“组成字符串ku的最大次数”问题

Python解决“组成字符串ku的最大次数”问题 问题描述测试样例解题思路代码 问题描述 给定一个字符串 s&#xff0c;该字符串中只包含英文大小写字母。你需要计算从字符串中最多能组成多少个字符串 “ku”。每次可以随机从字符串中选一个字符&#xff0c;并且选中的字符不能再使…...

ubuntu22使用TrinityCore搭建魔兽世界服务器

目录 一、Ubuntu22工具下载二、服务端编译配置1. 从 git 上拉取服务端代码2. 编译客户端3. 修改配置文件4. 加载sql语句5. 下载客户端6. 下载必要sql文件 三、客户端连接配置四、游戏启动 一、Ubuntu22工具下载 下载所有需要的工具 sudo apt-get update sudo apt-get install…...

LeetCode - 739.每日温度问题单调栈解法

目录 问题描述 方法思路&#xff1a;单调栈 核心思想 为什么用单调栈&#xff1f; 算法步骤 代码实现与逐行解析 示例解析 复杂度分析 总结 问题描述 给定一个整数数组 temperatures&#xff0c;表示每天的温度&#xff0c;返回一个数组 answer&#xff0c;其中 answe…...

GESP:2025-3月等级8-T1-上学

时间限制 : 1 秒 内存限制 : 128 MB C 城可以视为由 n个结点与 m条边组成的无向图。这些结点依次以1,2,....n标号&#xff0c;边依次以 1,2...m标号。第i条边&#xff08;1<i<m &#xff09;连接编号为ui 与vi的结点&#xff0c;长度为li米。 小 A 的学校坐落在 C 城中…...