【静态分析】软件分析课程实验A2-常量传播和Worklist求解器

news/2024/11/14 13:05:29/

Tai-e官网:

概述 | Tai-e

参考:

https://www.cnblogs.com/gonghr/p/17979609

--------------------------------------------------------

1 作业导览

  • 为 Java 实现常量传播算法。
  • 实现一个通用的 worklist 求解器,并用它来解决一些数据流分析问题,例如本次的常量传播。

在本次实验作业中,你要在 Tai-e 框架下实现常量传播算法和 worklist 求解器的关键部分。

2 实现常量传播

2.1 分析范围

在此次作业中,你需要实现针对 int 类型的常量传播。注意,在 Java 中,booleanbytecharshort 类型在运行时实际上都以 int 值的形式进行表示和计算,因此你的分析算法也应当能处理这些类型。其他基本数据类型(例如 longfloatdouble)以及引用类型(例如 class types、array types)不在此次作业的考虑范围内,所以你可以在分析时忽略它们的值。

至于语句的处理,你只需要关注等号左侧为变量且右侧只能是如下几类表达式的赋值语句:

  • 常量,如 x = 1
  • 变量,如 x = y
  • 二元运算表达式,如 x = a + bx = a >> b

下表列举出了除逻辑运算符以外所有作用在 int 类型上的二元运算符,请确保你的算法能正确处理它们:

运算类型运算符
Arithmetic+ - * / %
Condition== != < > <= >=
Shift<< >> >>>
Bitwise| & ^

逻辑运算符怎么办?

在上表中,我们没有列出逻辑运算符。这是因为,在 Java 中,逻辑运算符与(&&)和或(||)没有对应的字节码表示,而是以分支跳转的形式实现的。例如以下语句:

a = b || c;

将被转换为如下的语义等价的语句:

if (b) {t = true; // t is temp variable
} else if (c) {t = true;
} else {t = false;
}
a = t;

由于作业中的常量传播算法能够处理常量赋值(如 t = true)、变量赋值(如 a = t)和分支语句(如 if (b) {…}),所以它自然能够处理 &&||

至于等号左侧为变量、等号右侧为其它表达式的赋值语句,例如方法调用(x = m(...))和字段 load(x = o.f),你需要对它们进行保守的近似处理(也许会不够精确),即把它们当作 x = NAC。后续的作业将逐步解锁方法调用和字段访问的精确分析技巧。

对于上面没有提到的其它语句(例如字段存储 o.f = x),我们只需要使用恒等函数作为它们的 transfer 函数。在未来我们引入别名分析后,你就可以对字段存储进行精确处理了。

2.2 Tai-e 中你需要了解的类

这一节将会介绍与 IR 相关的类(pascal.taie.ir.*)以及和本次的分析算法相关的类(pascal.taie.analysis.*)。细致地了解它们将有助于完成常量分析算法。

  • pascal.taie.ir.IR

    这是 Tai-e 的 IR 的核心数据结构。它的每个实例储存了一个 Java 方法的各种信息,例如变量(variables)、参数(parameters)、语句(Stmts)等等。这里需要注意一点,一个方法体的中间表示由多个中间表示语句(Stmts)组成。如果想要加深理解,你可以自行阅读 API、源码实现以及注释。

  • pascal.taie.ir.exp.Exp

    我们已经在作业 1 中介绍过了这个接口。这是 Tai-e 的 IR 中的一个关键接口,用于表示程序中的所有表达式。它含有很多子类,对应各类具体的表达式。具体细节查看:【静态分析】软件分析课程实验A1-活跃变量分析和迭代求解器-CSDN博客

  • 在本次作业中,你需要处理更多 Exp 接口的子类。下图是一个继承关系的简单示意图(已略去与本次作业无关的类)。

  • 下面我们将逐一介绍这些子类。

  • pascal.taie.ir.exp.Var

    这个类代表 IR 中的变量。

  • pascal.taie.ir.exp.IntLiteral

    根据 Java 的语言规范,我们在 Tai-e 中把常量称作字面量(Literals)。每个 IntLiteral 类的实例都表示一个程序中的整数字面量。你可以通过调用 getValue() 方法来获取它的值。

  • pascal.taie.ir.exp.BinaryExp

    这个类代表程序中的二元表达式。这个类的各个子类对应了不同种类的二元表达式,并且每个子类中都有一个内部枚举类型用于表示该类支持的运算符。例如枚举类型 ArithmeticExp.Op 就代表了ArithmeticExp(算术表达式类)所支持的运算符,也就是 + - * /%

需要指出的是,在 Tai-e 中,BinaryExp 的两个操作数都是 Var 类型的。例如下面的语句

x = y + 6;

在 Tai-e 中会被转化成如下的 IR:

%intconst0 = 6;     // %intconst* are temp variables introduced
x = y + %intconst0; // by Tai-e to hold constant int values
  • 这样的设计简化了分析的实现:在获取 BinaryExp 的操作数时,你只需要考虑它是变量的这一种可能,而不用担心它是常量或其它可能了。

  • pascal.taie.ir.stmt.DefinitionStmt

    这是 Stmt 的一个子类。它表示了程序中所有的赋值语句,(即形如 x = yx = m(…) 的语句)。这个类很简单。你可以通过阅读源码来决定如何使用它。

  • pascal.taie.analysis.dataflow.analysis.DataflowAnalysis

    这是具体数据流分析算法需要实现的接口。和作业 1 一样,它会被求解器调用。在本次作业中你只需要关注前 5 个 API。这些 API 会被你在后面完成的 worklist 求解器调用。

  • pascal.taie.analysis.dataflow.analysis.constprop.Value

    这个类表示了常量分析中格上的抽象值。格的定义见【静态分析静态分析笔记04 - 数据流分析(理论)-CSDN博客

  • 它的代码和注释解释了它的用法。你应该用下列的静态方法获取格上抽象值(即该类的实例):

    • Value getNAC(): 返回 NAC
    • Value getUndef(): 返回 UNDEF
    • Value makeConstant(int): 返回给定整数在格上对应的抽象值
  • pascal.taie.analysis.dataflow.analysis.constprop.CPFact

    这个类表示常量传播中的 data facts,即一个从变量(Var)到格上抽象值(Value)的映射。该类提供了各种 map 相关的操作,例如键值对的查询、更新等等。这些操作大多继承自 pascal.taie.analysis.dataflow.fact.MapFact。这些类的注释都很充分,所以你应该通过阅读源码来决定如何使用其中的 API。

  • pascal.taie.analysis.dataflow.analysis.constprop.ConstantPropagation

    这个类实现了 DataflowAnalysis。你需要在其中编写完整的常量传播算法。具体要求见第 2.3 节。

2.3 你的任务 [重点!]

首先,你需要完成 ConstantPropagation 的下述 API:

  • CPFact newBoundaryFact(CFG)

  • CPFact newInitialFact()

  • void meetInto(CPFact,CPFact)

  • boolean transferNode(Stmt,CPFact,CPFact)

你已经在作业 1 中见到过这几个 API,他们是从 DataflowAnalysis 中继承下来的,需要注意的是:在实现 newBoundaryFact() 的时候,你要小心地处理每个会被分析的方法的参数。具体来说,你要将它们的值初始化为 NAC (请思考:为什么要这么做?)。

原因是为了 safe-approximation ,我们不知道通过形参传递过来的参数是否是常量,所以为了安全,假设所有参数都是 NAC ,当然这样会导致精度损失问题,后面通过过程间分析可以有效解决这个问题。

提示

正如第 2.1 节中提到的,本次作业只关注 int 类型的常量传播。为了实现这一点,框架代码在 ConstantPropagation 类中提供了 canHoldInt(Var) 方法来判断一个变量能否储存 int 类型的值。你需要利用这个方法来判断一个变量是否在本次作业的分析范围内,并忽略那些不在范围内的变量(例如 float 类型的变量)。

此外,你还需要实现下面两个辅助方法:

  • Value meetValue(Value,Value)

    该方法对应着格上的 meet 操作(⊓),详情见【静态分析静态分析笔记04 - 数据流分析(理论)-CSDN博客

  • 你应当在 meetInto() 方法中调用它。

  • Value evaluate(Exp,CPFact)

    这个方法会计算表达式(Exp)的值(Value)。当然,此处的值是格上的抽象值。你需要参考【静态分析静态分析笔记04 - 数据流分析(理论)-CSDN博客

  • 来实现它的三种情况。对于其它情况,该方法会像我们在第 2.1 节提到的那样返回 NAC你应该在 transferNode() 方法中调用它来进行表达式的求值。

提示

  1. 和作业 1 一样,我们对 meetInto() 的设计比较特殊。如果你不记得了,可以再回顾一下作业 1 文档的相关部分。
  2. 条件表达式(如 a == b)的值由 0(若为 False)和 1(若为 True)来表示。
  3. 对于除以 0 的情况(出现在 /% 中),我们规定结果为 UNDEF。例如,对于 x = a / 0x 的值将会是 UNDEF

算法伪代码描述

  • newBoundaryFact :负责创建和初始化虚拟结点的 Data Flow Fact。但是注意要把方法参数初始化为 NAC 。 根据题目要求,不是所有类型的参数都考虑,只有能转换成 int 类型的参数才考虑,所以别忘了用 canHoldInt 过滤一下。
  • newInitialFact :负责创建和初始化控制流图中除了 EntryExit 之外的结点的 Data Flow Fact。控制流图中一个结点的 INOUT 分别对应一个 Data Flow Fact ,记录当前程序点时变量的状态。直接创建一个空的 CPFact 即可,方法体内还没有开始扫描。
  • meetInto :负责处理 transfer function 之前可能遇到多个 OUT 时的合并处理。具体的合并操作通过调用 meetValue 来处理。
  • meetValue :负责对格上的值进行合并。

meet 操作

  • NAC ⊓ v = NAC(非常量)
  • UNDEF ⊓ v = v(未初始化的变量不是我们分析的目标)
  • c ⊓ v = ?
    • c ⊓ c = c
    • c1 ⊓ c2 = NAC
  • transferNode :负责实现控制流图中结点的 transfer function 。如果 OUT 改变,返回 true ;否则返回 false

stmt 表示结点中的一条中间表示,一个结点只有一个中间表示。

题目要求只需要对赋值语句处理,所以用 DefinitionStmt 类型过滤。

对于所有赋值语句,只考虑具有左值,并且左值是变量且类型可以转换成 int 的语句。这些语句的右值是一个表达式,可能是常量,也能是变量、二元表达式。这个右值表达式的值将通过 evaluate 函数计算。

对于其他类型的语句,不做处理,out 直接复制 in 即可,相当于经过一个恒等函数。

  • evaluate :负责表达式值的计算。

表达式分三种情况讨论

  1. 常量:直接赋值。
  2. 变量:获取变量的值再赋值。
  3. 二元运算:针对共 12 中运算分别处理。

/** Tai-e: A Static Analysis Framework for Java** Copyright (C) 2022 Tian Tan <tiantan@nju.edu.cn>* Copyright (C) 2022 Yue Li <yueli@nju.edu.cn>** This file is part of Tai-e.** Tai-e is free software: you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License* as published by the Free Software Foundation, either version 3* of the License, or (at your option) any later version.** Tai-e is distributed in the hope that it will be useful,but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General* Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with Tai-e. If not, see <https://www.gnu.org/licenses/>.*/package pascal.taie.analysis.dataflow.analysis.constprop;import pascal.taie.analysis.dataflow.analysis.AbstractDataflowAnalysis;
import pascal.taie.analysis.graph.cfg.CFG;
import pascal.taie.config.AnalysisConfig;
import pascal.taie.ir.IR;
import pascal.taie.ir.exp.ArithmeticExp;
import pascal.taie.ir.exp.BinaryExp;
import pascal.taie.ir.exp.BitwiseExp;
import pascal.taie.ir.exp.ConditionExp;
import pascal.taie.ir.exp.Exp;
import pascal.taie.ir.exp.IntLiteral;
import pascal.taie.ir.exp.ShiftExp;
import pascal.taie.ir.exp.Var;
import pascal.taie.ir.stmt.DefinitionStmt;
import pascal.taie.ir.stmt.Stmt;
import pascal.taie.language.type.PrimitiveType;
import pascal.taie.language.type.Type;
import pascal.taie.util.AnalysisException;import pascal.taie.ir.exp.*;public class ConstantPropagation extendsAbstractDataflowAnalysis<Stmt, CPFact> {public static final String ID = "constprop";public ConstantPropagation(AnalysisConfig config) {super(config);}@Overridepublic boolean isForward() {return true;}@Overridepublic CPFact newBoundaryFact(CFG<Stmt> cfg) {// TODO - finish meCPFact cpFact = new CPFact();for (Var param : cfg.getIR().getParams()) {if (canHoldInt(param)) {                   // 只考虑可转换int类型的参数cpFact.update(param, Value.getNAC());  // 建立参数到格上值(NAC)的映射}}return cpFact;}@Overridepublic CPFact newInitialFact() {// TODO - finish mereturn new CPFact();}@Overridepublic void meetInto(CPFact fact, CPFact target) {// TODO - finish mefor (Var var : fact.keySet()) {Value v1 = fact.get(var);Value v2 = target.get(var);target.update(var, meetValue(v1, v2));}}/*** Meets two Values.*/public Value meetValue(Value v1, Value v2) {// TODO - finish meif (v1.isNAC() || v2.isNAC()) {return Value.getNAC();} else if (v1.isUndef()) {return v2;} else if (v2.isUndef()) {return v1;} else if (v1.isConstant() && v2.isConstant()) {if (v1.getConstant() == v2.getConstant()) {return v1;} else {return Value.getNAC();}} else {return Value.getNAC();}}@Overridepublic boolean transferNode(Stmt stmt, CPFact in, CPFact out) {// TODO - finish meCPFact copy = in.copy();   // 复制in给copy,避免影响in。if (stmt instanceof DefinitionStmt) { // 只处理赋值语句if (stmt.getDef().isPresent()) {  // 如果左值存在LValue lValue = stmt.getDef().get();  // 获取左值if ((lValue instanceof Var) && canHoldInt((Var) lValue)) {  // 对于符合条件的左值copy.update((Var) lValue, evaluate(((DefinitionStmt<?, ?>)  stmt).getRValue(), copy));  // 计算右值表达式的值用来更新左值变量在格上的值}}}return out.copyFrom(copy);  // copy复制给out。copy和in相比,有更新,返回true;反之返回false}/*** @return true if the given variable can hold integer value, otherwise false.*/public static boolean canHoldInt(Var var) {Type type = var.getType();if (type instanceof PrimitiveType) {switch ((PrimitiveType) type) {case BYTE:case SHORT:case INT:case CHAR:case BOOLEAN:return true;}}return false;}/*** Evaluates the {@link Value} of given expression.** @param exp the expression to be evaluated* @param in  IN fact of the statement* @return the resulting {@link Value}*/public static Value evaluate(Exp exp, CPFact in) {// TODO - finish meif (exp instanceof Var) {   // 变量return in.get((Var) exp);} else if (exp instanceof IntLiteral) {  // 常量return Value.makeConstant(((IntLiteral) exp).getValue());} else if (exp instanceof BinaryExp) {   // 二元运算Value v1 = in.get(((BinaryExp) exp).getOperand1()); // 获取运算分量在格上的值Value v2 = in.get(((BinaryExp) exp).getOperand2());if (v1.isNAC() || v2.isNAC()) {if (v1.isNAC() && v2.isConstant() && exp instanceof ArithmeticExp) {  // x = a / 0,x = a % 0,x 的值将会是 UNDEFArithmeticExp.Op operator = ((ArithmeticExp) exp).getOperator();if (operator == ArithmeticExp.Op.DIV || operator == ArithmeticExp.Op.REM) {if (v2.getConstant() == 0) return Value.getUndef();}}return Value.getNAC();}if (v1.isUndef() || v2.isUndef()) {return Value.getUndef();}if (exp instanceof ArithmeticExp) {ArithmeticExp.Op operator = ((ArithmeticExp) exp).getOperator();switch (operator) {case ADD -> {return Value.makeConstant(v1.getConstant() + v2.getConstant());}case DIV -> {if (v2.getConstant() == 0) return Value.getUndef();return Value.makeConstant(v1.getConstant() / v2.getConstant());}case MUL -> {return Value.makeConstant(v1.getConstant() * v2.getConstant());}case SUB -> {return Value.makeConstant(v1.getConstant() - v2.getConstant());}case REM -> {if (v2.getConstant() == 0) return Value.getUndef();return Value.makeConstant(v1.getConstant() % v2.getConstant());}}} else if (exp instanceof ConditionExp) {ConditionExp.Op operator = ((ConditionExp) exp).getOperator();switch (operator) {case EQ -> {if (v1.getConstant() == v2.getConstant()) return Value.makeConstant(1);else return Value.makeConstant(0);}case GE -> {if (v1.getConstant() >= v2.getConstant()) return Value.makeConstant(1);else return Value.makeConstant(0);}case GT -> {if (v1.getConstant() > v2.getConstant()) return Value.makeConstant(1);else return Value.makeConstant(0);}case LE -> {if (v1.getConstant() <= v2.getConstant()) return Value.makeConstant(1);else return Value.makeConstant(0);}case LT -> {if (v1.getConstant() < v2.getConstant()) return Value.makeConstant(1);else return Value.makeConstant(0);}case NE -> {if (v1.getConstant() != v2.getConstant()) return Value.makeConstant(1);else return Value.makeConstant(0);}}} else if (exp instanceof BitwiseExp) {BitwiseExp.Op operator = ((BitwiseExp) exp).getOperator();switch (operator) {case OR -> {return Value.makeConstant(v1.getConstant() | v2.getConstant());}case AND -> {return Value.makeConstant(v1.getConstant() & v2.getConstant());}case XOR -> {return Value.makeConstant(v1.getConstant() ^ v2.getConstant());}}} else if (exp instanceof ShiftExp) {ShiftExp.Op operator = ((ShiftExp) exp).getOperator();switch (operator) {case SHL -> {return Value.makeConstant(v1.getConstant() << v2.getConstant());}case SHR -> {return Value.makeConstant(v1.getConstant() >> v2.getConstant());}case USHR -> {return Value.makeConstant(v1.getConstant() >>> v2.getConstant());}}}else {  // 二元表达式中的其他类型表达式return Value.getNAC();}}return Value.getNAC();}
}

3 实现 Worklist 求解器

3.1 Tai-e 中你需要了解的类

与迭代求解器类似,你需要清楚 DataflowResultCFGSolver 的相关用法(我们已经在作业 1 中介绍过了)。除此之外,你还需要知道:

  • pascal.taie.analysis.dataflow.solver.WorkListSolver

    该类继承了 Solver 类,实现了 worklist 算法。它的实现是不完整的,在本此作业中你需要完成它。

3.2 你的任务 [重点!]

你的第二个任务是完成下述两个 API 的实现:

考虑到常量传播是一个前向分析,你只需要关注前向分析相关的方法。initializeForward() 方法的具体实现参考如图前三行。

doSolveForward() 则包含了你要实现的算法的主体部分。

  • Solver.initializeForward(CFG,DataflowResult)
  • WorkListSolver.doSolveForward(CFG,DataflowResult)

提示

  1. 讲义中的 worklist 算法通过比较 old_OUTOUT[B] 来决定后继节点是否应当加入 worklist 中,这个做法比较低效。Tai-e 中 DataflowAnalysis.transferNode() 会返回此次 transfer 是否改变了 OUT fact。利用好这一点可以避免多余的判断;
  2. 与作业 1 类似,不要忘了在 Solver.initializeForward() 中初始化每个语句的 INOUT

initializeForward :初始化所有的 Data Flow Fact

doSolveForward :负责实现 Worklist 求解器具体步骤。

protected void initializeForward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {// TODO - finish meresult.setOutFact(cfg.getEntry(), analysis.newBoundaryFact(cfg));for (Node node : cfg) {if (cfg.isEntry(node)) continue;result.setInFact(node, analysis.newInitialFact());result.setOutFact(node, analysis.newInitialFact());}}
/** Tai-e: A Static Analysis Framework for Java** Copyright (C) 2022 Tian Tan <tiantan@nju.edu.cn>* Copyright (C) 2022 Yue Li <yueli@nju.edu.cn>** This file is part of Tai-e.** Tai-e is free software: you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License* as published by the Free Software Foundation, either version 3* of the License, or (at your option) any later version.** Tai-e is distributed in the hope that it will be useful,but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General* Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with Tai-e. If not, see <https://www.gnu.org/licenses/>.*/package pascal.taie.analysis.dataflow.solver;import pascal.taie.analysis.dataflow.analysis.DataflowAnalysis;
import pascal.taie.analysis.dataflow.fact.DataflowResult;
import pascal.taie.analysis.graph.cfg.CFG;import java.util.*;class WorkListSolver<Node, Fact> extends Solver<Node, Fact> {WorkListSolver(DataflowAnalysis<Node, Fact> analysis) {super(analysis);}@Overrideprotected void doSolveForward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {// TODO - finish meArrayDeque<Node> worklist = new ArrayDeque<>();   // 双端堆栈当队列用for (Node node : cfg) {   // 添加所有结点到队列中if (cfg.isEntry(node)) {continue;}worklist.addLast(node);}while (!worklist.isEmpty()) {Node node = worklist.pollFirst();  // 弹出队头结点for (Node pred : cfg.getPredsOf(node)) {  // 对该结点以及所有前驱结点的OUT做meetanalysis.meetInto(result.getOutFact(pred), result.getInFact(node));}boolean f = analysis.transferNode(node, result.getInFact(node), result.getOutFact(node));if (f) {  // 如果该节点OUT发生了变化,将其所有后继节点添加到队列for (Node succ : cfg.getSuccsOf(node)) {worklist.addLast(succ);}}}}@Overrideprotected void doSolveBackward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {throw new UnsupportedOperationException();}
}

4 运行与测试

你可以按我们在 Tai-e 框架(教学版)配置指南 中提到的方式来运行分析。在本作业中,Tai-e 对输入类的每个方法进行常量传播分析,并输出分析的结果,也就是每个语句的 OUT fact 所包含的数据流信息(变量的格上对应值)。

--------------------<Assign: void assign()> (constprop)--------------------

[0@L4] x = 1; null

[1@L5] x = 2; null

[2@L6] x = 3; null

[3@L7] x = 4; null

[4@L8] y = x; null

[5@L8] return; null

当你未完成作业的时候,OUT fact 的结果为 null,当你完成了所有空缺代码后,分析的输出应当形如:

--------------------<Assign: void assign()> (constprop)--------------------

[0@L4] x = 1; {x=1}

[1@L5] x = 2; {x=2}

[2@L6] x = 3; {x=3}

[3@L7] x = 4; {x=4}

[4@L8] y = x; {x=4, y=4}

[5@L8] return; {x=4, y=4}

此外,Tai-e 将被分析方法的控制流图输出到 output/ 文件夹,它们被存储为 .dot 文件,你可以用可视化工具Graphviz来查看这些控制流图。

我们为本次作业提供了测试驱动类 pascal.taie.analysis.dataflow.analysis.constprop.CPTest,你可以按照 Tai-e 框架(教学版)配置指南 中描述的方式来测试你的实现是否正确。


http://www.ppmy.cn/news/1460745.html

相关文章

免费PDF批量加密工具

最近在找PDF批量加密的软件来着&#xff0c;发现很多都是需要收费的&#xff0c;当然如果平时工作需要用的比较多&#xff0c;支持一下还是ok的&#xff0c;但是多数人还是偶尔用一下所以没有必要买。 工作用的话&#xff0c;一般企业文件、个人隐私资料、重要合同...所有重要文…

RateLimiter 限流算法使用

Rate Limiter 是一种限流算法&#xff0c;用于控制系统中的资源使用率。它可以用于限制用户的访问速度&#xff0c;防止系统被过度访问&#xff0c;从而导致系统崩溃或性能下降。 通常用于网络服务、 API 接口和数据库等领域&#xff0c;以确保系统的稳定性和可靠性。 原理 R…

深度学习之视觉特征提取器——AlexNet

AlexNet 参考资料&#xff1a; &#xff08;1&#xff09;ImageNet十年历任霸主之AlexNet - 知乎 (zhihu.com) &#xff08;2&#xff09;AlexNet - Wikipedia 引入 AlexNet在2012年以第一名在Top-1分类精度霸榜ImageNet&#xff0c;并超过第二名近10个百分点&#xff0c;…

《二十一》QT QML编程基础

QML概述 QML&#xff08;Qt Meta-Object Language&#xff09;是一种声明性语言&#xff0c;它被用于描述Qt框架中用户界面的结构和行为。QML提供了一种简洁、灵活的方式来创建动态和交互式的界面。 QML基于JavaScript语法&#xff0c;通过使用QML类型和属性来定义界面的元素…

[思考记录]问题也是发现和改进自身的契机

这次xx产品新版本的试点项目上出现了升级后被迫回退版本的情况。其主要原因是新版本的登录模块引入了一个新的组件依赖&#xff0c;需要安装较新版本的 vc_redist 组件后才能正常使用。然而&#xff0c;这个问题在测试和发版阶段并未被发现&#xff0c;直到用户环境中才体现出来…

【全开源】Java同城预约月嫂服务上门服务本地服务源码APP+小程序+公众号+H 5

智能匹配与推荐&#xff1a;源码运用先进的算法和定位技术&#xff0c;根据用户的需求和地理位置&#xff0c;智能匹配并推荐附近的合适月嫂。这种匹配不仅基于地理位置&#xff0c;还考虑了月嫂的技能、经验、评价等因素&#xff0c;确保服务的质量和可靠性。 在线预约与支付…

intel owt sdk windows编译中的坑

visual studio 单步调试OWT webrtc client环境搭建_owt-client只编译webrtc-CSDN博客 1.gclient sync失败错误&#xff0c;our depot_tools checkout is configured to fetch from an obsolete URL 解决方法&#xff1a;下载最新版本depot_tools https://storage.googleapis…

shell脚本中条件语句

一.test测试 在Shell脚本中&#xff0c;test命令用于进行条件测试。它也可以通过方括号[]来表示&#xff0c;因为test实际上是[命令的一个别名。 格式1&#xff1a;test 条件表达式 格式2&#xff1a;[ 条件表达式 ] 注意[ ]空格&#xff0c;否则会失败 测试 是否成功使用…