静态测试---基于WorkList的活跃变量分析
本文主要用于记录在活跃变量分析实验中的报错及解决,涉及静态测试的详细原理内容较少,编译运行底层逻辑偏多。
一、实验要求
1)使用llvm基于框架实现一个基于WorkList的活跃变量分析demo。变量在某个程序点有两种状态,live 或 dead。对于变量 x 和程序点 p,判断 x 在点 p 上的值是否会在 CFG 中的某条从点 p 出发的路径中使用。如果是,就说 x 在 p 上 live;否则就说x在p上是 dead。
目前本实验假设的输入程序当中只有整型变量(即,没有浮点、指针、结构、枚举、数组等)。
即实现LivenessAnalysis中的如下函数:
- void LivenessPass::flowIn(Instruction *Inst, BitVector InVec)
- void LivenessPass::flowOut(Instruction *Inst, BitVector Pre, BitVector Post)
- BitVector LivenessPass::transfer(Instruction *Inst)
- void LivenessPass::InstAnalysis()
这些函数对应如下Worklist
算法当中的如下四个部分:
活跃变量分析为后向的数据流分析,本实验实现的demo将通过一个Worklist
的大循环对LLVM IR
当中的每条指令进行活跃变量的计算,其中计算的结果将以llvm::BitVector
的形式存储在std::map<Value *, BitVector> INSet\OUTSet
当中。
上述输出为最终预期的输出结果,其中右侧为每个指令对应的活跃变量分析结果,以 [ INSet[inst] --> OUTSet[inst] ]
形式表现,且由于是后向分析,所以最后右侧的输出结果是从下到上的形式表现的。
二、实验内容:
2.1实验环境:
2.1.1环境配置:
安装LLVM 10.0.1
:
- ##llvm10.0.1安装
- wget https://github.com/llvm/llvm-project/archive/llvmorg-10.0.1.tar.gz
- tar -zxvf llvmorg-10.0.1.tar.gz
- cd llvm-project
- mkdir build
- cd build
- cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=llvm_install_dir ../llvm
- make -j 4 ##4为编译运行时的线程数
- sudo make install
- ##若未安装到默认目录当中,需要在~/.bashrc当中添加路径:
- sudo gedit ~/.bashrc
- ##在文本末尾添加:
- export PATH="$PATH:llvm_install_dir/bin"
- ##更新shell:
- source ~/.bashrc
安装完环境后,在实验框架目录下执行以下指令编译运行:
- mkdir build
- cd build
- cmake ..
- make
若能够编译成功,你将在build
目录下见到LivenessPass.so
。 之后在test目录下运行./auto.sh test01
得到以下输出:
- Running Liveness on main
- BitVec map:
- Liveness Analysis Result:
- %retval = alloca i32, align 4: [ --> ]
- %a = alloca i32, align 4: [ --> ]
- %b = alloca i32, align 4: [ --> ]
- %c = alloca i32, align 4: [ --> ]
- %d = alloca i32, align 4: [ --> ]
- store i32 0, i32* %retval, align 4: [ --> ]
- %0 = load i32, i32* %a, align 4: [ --> ]
- %1 = load i32, i32* %b, align 4: [ --> ]
- %add = add nsw i32 %0, %1: [ --> ]
- store i32 %add, i32* %d, align 4: [ --> ]
- %2 = load i32, i32* %d, align 4: [ --> ]
- store i32 %2, i32* %b, align 4: [ --> ]
- %3 = load i32, i32* %a, align 4: [ --> ]
- store i32 %3, i32* %c, align 4: [ --> ]
- ret i32 0: [ --> ]
2.1.2环境情况:
Ubuntu:
Linux:
Llvm:
2.2 flowIn实现:
2.2.1文件框架:
// Inst:当前处理的Inst; InVec:当前处理的Inst的前一轮分析的Input Bitvectorvoid LivenessPass::flowIn(Instruction *Inst, BitVector InVec) {///TODO 将所有上一个Inst(由于是后向分析,所以是当前Inst的后继Inst)的Output Bitvector进行join操作作为当前Inst的Input Bitvectorauto successors = getSuccessors(Inst);BitVector InputVec = InVec;// 遍历后继指令,并进行合并操作for (auto successor : successors) {BitVector OutputVec = OUTSet[successor];InputVec |= OutputVec;}INSet[Inst] = InputVec;// 将合并后的结果设置为当前指令的输入位向量
}
- LivenessPass::flowIn(Instruction *Inst, BitVector InVec):
这是一个成员函数,用于计算指定指令 Inst 的输入位向量 InVec。输入位向量 InVec 表示在 Inst 执行之前活跃的变量集合。函数的目标是将所有 Inst 的后继指令的输出位向量进行合并(join操作),得到 Inst 的输入位向量。
- auto successors = getSuccessors(Inst);:
获取 Inst 的所有后继指令。在控制流图中,后继指令是指在控制流中直接跟随 Inst 的指令。
- BitVector InputVec = InVec;:
创建了一个新的位向量 InputVec,它是 InVec 的副本,用于存储 Inst 的最终输入位向量。
- for (auto successor : successors) { ... }:
这个循环遍历 Inst 的所有后继指令。对于每个后继指令,它获取其输出位向量 OutputVec,这是在 successor 执行之后活跃的变量集合。
- InputVec |= OutputVec;:
执行位向量的逻辑或操作,将 OutputVec 合并到 InputVec 中。合并操作是将所有后继指令的输出位向量合并到当前指令的输入位向量中,表示这些变量在 Inst 执行之前也是活跃的。
- INSet[Inst] = InputVec;:
计算得到的输入位向量 InputVec 存储在 INSet 映射中,INSet 用于存储每个指令的输入位向量。
- bool bitVectorsEqual(const BitVector& vec1, const BitVector& vec2):
用于比较两个位向量是否相等。它首先检查两个位向量的长度是否相同,如果不同,则它们不相等。然后,它遍历位向量的每个位,如果发现任何不匹配的位,则返回 false。如果所有位都匹配,则返回 true
2.3 flowOut实现 :
void LivenessPass::flowOut(Instruction *Inst, BitVector Pre, BitVector Post){
///TODO
//判断当前输出的Output Bitvector和上一轮迭代的Output Bitvector是否一致,
//如果不一致就将当前Inst的所有下一个Inst(由于是后向分析,所以是当前Inst的前继Inst)加入InstVector当中if(bitVectorsEqual(Pre,Post) ){//Pass;}else{auto nextnodes = getPredecessors(Inst);for(auto eachnodes : nextnodes)InstVector.push_back(eachnodes);}OUTSet[Inst] = Post;//?
}
- void LivenessPass::flowOut(Instruction *Inst, BitVector Pre, BitVector Post):
这个成员函数接受三个参数:Inst 是当前正在处理的指令,Pre 是指令执行前的位向量(即输入位向量),Post 是指令执行后的位向量(即输出位向量)。
- if(bitVectorsEqual(Pre, Post)):
用于检查当前指令的输出位向量 Post 是否与上一轮迭代的输出位向量 Pre 相同。如果它们相同,说明对于当前指令的输出位向量没有新的信息,因此不需要进行任何操作。
- else:
如果输出位向量发生了变化,即 Pre 和 Post 不相同,那么需要更新依赖于当前指令的其他指令的活性信息。
- auto nextnodes = getPredecessors(Inst); 获取当前指令的所有前继指令。
- for(auto eachnodes : nextnodes) InstVector.push_back(eachnodes); 将这些前继指令加入到 InstVector 中,这是一个待处理的指令列表,用于后续的迭代。
- OUTSet[Inst] = Post;:
更新当前指令 Inst 的输出位向量 OUTSet 为新的值 Post。OUTSet 是一个映射,用于存储每个指令的输出位向量。
2.4 transfer实现:
BitVector LivenessPass::transfer(Instruction *Inst) {// 实现活跃变量分析的转移函数// 初始化Use和Def集合BitVector Use = BitVector(InstCounter, false);BitVector Def = BitVector(InstCounter, false);// 根据指令类型进行分类处理if (auto *BinOp = dyn_cast<BinaryOperator>(Inst)) {Value *Operand0 = BinOp;Value *Operand1 = BinOp->getOperand(0);Value *Operand2 = BinOp->getOperand(1);Def[InstBitMap[Operand0]] = true;if (!isImmutable(Operand1))Use[InstBitMap[Operand1]] = true;if (!isImmutable(Operand2))Use[InstBitMap[Operand2]] = true;} else if (auto *Load = dyn_cast<LoadInst>(Inst)) {Value *Operand0 = Load;Value *Operand1 = Load->getOperand(0);Def[InstBitMap[Operand0]] = true;if (!isImmutable(Operand1))Use[InstBitMap[Operand1]] = true;} else if (auto *Store = dyn_cast<StoreInst>(Inst)) {Value *Operand0 = Store->getOperand(0);Value *Operand1 = Store->getOperand(1);Def[InstBitMap[Operand1]] = true;if (!isImmutable(Operand0))Use[InstBitMap[Operand0]] = true;} else if (auto *Alloc = dyn_cast<AllocaInst>(Inst)) {Value *Operand0 = Alloc;Def[InstBitMap[Operand0]] = true;} else if (auto *Cmp = dyn_cast<ICmpInst>(Inst)) {Value *Operand0 = Cmp;Value *Operand1 = Cmp->getOperand(0);Value *Operand2 = Cmp->getOperand(1);Def[InstBitMap[Operand0]] = true;if (!isImmutable(Operand1))Use[InstBitMap[Operand1]] = true;if (!isImmutable(Operand2))Use[InstBitMap[Operand2]] = true;}// 计算输出活跃变量集合BitVector Out = BitVector(InstCounter, false);BitVector Temp = INSet[Inst];for (int i = 0; i < Temp.size(); ++i) {if (Def[i])Temp[i] = false;}for (int i = 0; i < Use.size(); ++i) {if (Use[i] || Temp[i])Out[i] = true;}return Out;
}
这段代码是一个活跃变量分析(Liveness Analysis)的迁移函数transfer的实现。迁移函数用于计算在执行当前指令后,哪些变量是活跃的。活跃变量是指在指令执行后仍然有效的变量,它们可能是使用(Use)或定义(Def)的变量。
代码的主要部分是针对不同类型的指令(二元运算符、加载指令、存储指令、内存分配指令、比较指令)的分支处理。对于每种类型的指令,代码会更新Use和Def集合,然后使用这些集合来计算输出活跃变量集合。
- BitVector Use = BitVector(InstCounter, false); 和 BitVector Def = BitVector(InstCounter, false);:
这两行代码初始化了两个位向量 Use 和 Def,它们分别用于表示指令的“使用”集合和“定义”集合。InstCounter 是指令的数量,用于确定位向量的长度。初始化时,位向量的所有位都设置为 false。
- if (auto *BinOp = dyn_cast<BinaryOperator>(Inst)) { ... }:
这段代码处理二元运算符指令。dyn_cast 是一个类型转换函数,用于安全地将指令转换为 BinaryOperator 类型。如果转换成功,说明当前指令是一条二元运算符指令。
对于二元运算符,它的结果(Operand0)被加入到 Def 集合中,表示这个指令定义了结果值。如果操作数(Operand1 和 Operand2)不是不可变的(isImmutable 返回 false),则它们被加入到 Use 集合中,表示这些操作数被使用了。
- else if (auto *Load = dyn_cast<LoadInst>(Inst)) { ... }:
这段代码处理加载指令(Load)。加载指令的结果被加入到 Def 集合中,表示这个指令定义了加载的值。加载指令的源操作数(Operand1)如果是可变的,则被加入到 Use 集合中。
- else if (auto *Store = dyn_cast<StoreInst>(Inst)) { ... }:
处理存储指令(Store)。存储指令的目标操作数(Operand1)被加入到 Def 集合中,表示这个指令定义了存储的目标。存储指令的源操作数(Operand0)如果是可变的,则被加入到 Use 集合中。
- else if (auto *Alloc = dyn_cast<AllocaInst>(Inst)) { ... }:
处理分配指令(Alloca)。
分配指令的结果被加入到 Def 集合中,表示这个指令定义了分配的内存。
- else if (auto *Cmp = dyn_cast<ICmpInst>(Inst)) { ... }:
处理比较指令(ICmp)。
比较指令的结果被加入到 Def 集合中,表示这个指令定义了比较的结果。
比较指令的操作数(Operand1 和 Operand2)如果是可变的,则被加入到 Use 集合中。
- BitVector Out = BitVector(InstCounter, false);:
初始化了输出位向量 Out,它将用于存储当前指令执行后的活跃变量集合。
- BitVector Temp = INSet[Inst];:
获取了当前指令的输入位向量 INSet[Inst],并将其存储在临时位向量 Temp 中。
- for (int i = 0; i < Temp.size(); ++i) { ... }:
遍历临时位向量 Temp,如果某个位在 Def 集合中为 true,则将该位在 Temp 中设置为 false。
这是因为如果一个变量在当前指令中被定义,那么它之前的活跃状态就不重要了。
- for (int i = 0; i < Use.size(); ++i) { ... }:
遍历 Use 集合,如果某个位在 Use 集合中为 true 或者该位在 Temp 中为 true,则将该位在 Out 中设置为 true。
这表示这个变量在当前指令执行后仍然是活跃的。
- return Out;:
最后,返回输出位向量 Out,它表示当前指令执行后的活跃变量集合。
2.5 InstAnalysis实现:
初次尝试:
结果:
更新后,成功!:
void LivenessPass::InstAnalysis() {///TODO///基于实现基本的WorkList循环,使用你刚刚实现的flowIn, flowOut, transfer函数实现活跃变量分析while (!InstVector.empty()){auto instruction = *InstVector.begin(); // 获取指向 Value* 的指针,注意*号,否则返回的是迭代器Instruction *Inst = dyn_cast<Instruction>(instruction);InstVector.erase(InstVector.begin());if (Inst) // 确保转换成功{BitVector Pre = OUTSet[instruction];flowIn(Inst, INSet[instruction]);OUTSet[instruction] = transfer(Inst); //transfer函数导致段错误flowOut(Inst, Pre, OUTSet[instruction]);}
代码解释如下:
- void LivenessPass::InstAnalysis():
成员函数,用于执行活跃变量分析。
- for (auto instIterator = InstVector.begin(); instIterator != InstVector.end(); ):
遍历指令集合 InstVector。
InstVector 是一个待处理的指令列表,包含了在活跃变量分析中需要考虑的指令。
- Instruction *Inst = dyn_cast<Instruction>(*instIterator);:
尝试将迭代器指向的元素转换为 Instruction 类型的指针。
dyn_cast 是一个类型转换函数,用于安全地将迭代器指向的元素转换为 Instruction 类型。
- instIterator = InstVector.erase(instIterator);:
如果转换成功,这行代码从 InstVector 中移除当前指令。
移除操作通常发生在指令被处理后,以确保在后续迭代中不会重复处理同一个指令。
- if (Inst) // 确保转换成功:
用于确保转换成功,即当前迭代器指向的元素确实是一个指令。
- BitVector &preOut = OUTSet[Inst];:
获取当前指令的输出位向量 preOut。
OUTSet 是一个映射,用于存储每个指令的输出位向量。
- flowIn(Inst, INSet[Inst]);:
调用 flowIn 函数,计算当前指令的输入位向量。
INSet 是一个映射,用于存储每个指令的输入位向量。
- OUTSet[Inst] = transfer(Inst);:
更新当前指令的输出位向量。
transfer 函数用于计算指令的输出位向量,即在该指令执行后活跃的变量集合。
- flowOut(Inst, preOut, OUTSet[Inst]);:
调用 flowOut 函数,计算当前指令的前继指令的输出位向量。
preOut 是当前指令在执行前的输出位向量,OUTSet[Inst] 是当前指令在执行后的输出位向量。
2.6 测试结果:
三、补充内容:
报错1:no LivevessPass.so
.SO文件是共享对象文件(Shared Object File)的缩写,它是一种用于链接的文件格式,其中包含了可执行代码和数据,这些代码和数据可以被多个程序共享。.SO文件通常用于Unix-like操作系统,如Linux,它们用于动态链接,允许应用程序在运行时链接到这些文件,而不是在编译时链接。
这让我想起来之前学习时用的.a静态库,查阅资料发现:
- .SO文件:
- 文件扩展名:.so(共享对象)
- 适用平台:Unix-like操作系统,如Linux
- 文件内容:.so文件包含动态链接库(Dynamic Link Library)的代码和数据,它们可以在程序运行时被加载到内存中。.so文件通常用于提供扩展功能,如图形库、网络库等。
- .a文件:
- 文件扩展名:.a(静态链接库)
- 适用平台:Unix-like操作系统,如Linux
- 文件内容:.a文件包含静态链接库的代码和数据,它们在编译时被链接到程序中,而不是在运行时。.a文件通常用于提供基础功能,如标准库。
- .dll文件:
- 文件扩展名:.dll(动态链接库)
- 适用平台:Windows操作系统
- 文件内容:.dll文件包含动态链接库的代码和数据,它们可以在程序运行时被加载到内存中。.dll文件通常用于提供扩展功能,如图形库、网络库等。
- 在功能上,.SO和.dll文件都是用于动态链接的库文件,而.a文件是用于静态链接的库文件。静态链接和动态链接的主要区别在于,静态链接将库文件的内容直接合并到可执行文件中,而动态链接则将库文件作为外部依赖,在运行时加载。
- 相关性方面,.SO和.dll文件都是为了在运行时提供扩展功能,而.a文件则提供了基础功能。在实际应用中,.SO和.dll文件可以被看作是.a文件的动态版本,它们提供了相同的库功能,但以不同的方式集成到应用程序中。
解决1:
原因是没有camke .. 和make编译…..
相关资料学习:
- CMake:
- 用途: CMake是一个跨平台的构建系统生成器,它用于生成可执行的构建系统,如Makefile。
- 生成: CMake不直接构建项目,而是生成一个构建系统,如Makefile或Ninja构建文件,然后使用Make或Ninja来构建项目。
- 平台: CMake支持多种平台,包括Windows、macOS和Linux。
- 配置: CMake通过一个名为CMakeLists.txt的配置文件来定义项目配置,包括依赖关系、编译选项等。
- 构建系统: CMake生成的构建系统(如Makefile)可以由Make或Ninja使用来构建项目。
- Make:
- 用途: Make是一个构建工具,用于自动化编译过程。
- 直接构建: Make直接构建项目,根据Makefile中的规则和依赖关系来编译源代码。
- 平台: Make在Unix-like操作系统中广泛使用,但在其他平台上也有替代品。
- 构建系统: Makefile是由Make使用来构建项目的文件,它定义了项目的依赖关系和编译规则。
成功解决。
报错2: make报错(代码内变量未定义):
尝试2.1:改头文件(and make文件里的include)
看到众多未定义,我以为是头文件库的包含有误;
改动为:
但无效,查看cmake发现:
已经包含了include:
include_directories(${LLVM_INCLUDE_DIRS} include):将LLVM的头文件目录和项目中的include目录添加到编译器的头文件搜索路径中。
解决2:
实际是我的代码编写有误,确实没定义该内容;应该是livenesspass;
报错3: ./auto.sh 脚本内对输入处理有误
解决3:先输入一个符号避免报错,随后输入测试文件名;
Cat查看内容,如下:
同样需要换行后才能输入(为什么?)
报错4:./auto.sh执行完没有输出:
尝试4.1:print函数是否修改
教辅表示查看print,但是我并没有改动过其他print函数的文件.
解决4: 恢复原auto.sh
恢复原auto.sh.查看区别:
3,4行的cd原因见【报错5】,测试路径是在/myshixun下;
5,6行是把正确和错误输出都重定向到null;
第9行是问题关键: 没有-Liveness是没有正确输出的;
opt 是 LLVM 优化器的一个命令行工具,它用于对 LLVM IR(中间表示)进行各种优化。LivenessPass.so 是一个共享库,它包含了一个自定义的 LLVM pass(优化过程),这个 pass 被命名为 Liveness。
寻找Liveness,两个文件下找到:
加入Liveness后,成功得到输出:
报错5:头哥平台评测错误:
报错显示没有LivenessPass.so这个共享库(类似动态库);
根据之前的试错中得知这个是make后生成的----定位错误到make环节;
解决5:
pwd先看本地路径是什么:
发现是make失败,即代码有误(见报错2)
细看,发现报错是中括号中的变量处理有误:
对比成功代码,发现是:
我使用迭代器instIterator
来访问InstVector
中元素,并使用它作为OUTSet
和INSet
的索引。而std::map
不支持使用迭代器作为索引。
应该使用Inst
作为索引,因为Inst
是一个指向Instruction
的指针,这是OUTSet
和INSet
中键的类型:
且这里的代码也同样出错【InstVector是向量vector,erase操作没有返回值;】:
探索6:命令行输出不一:
发现: 此处make时只有2个cpp.o:
而之前都是4个:
尝试在更改一个cpp文件后,直接make:---发现只有一个cpp.o---说明只会重新make更改过的文件【除非make clean 了 会重新make所有】
报错7:段错误【代码导致的】
是指针处的问题;(具体细节见前面【报错 4】)
相关文章:

静态测试---基于WorkList的活跃变量分析
本文主要用于记录在活跃变量分析实验中的报错及解决,涉及静态测试的详细原理内容较少,编译运行底层逻辑偏多。 一、实验要求 1)使用llvm基于框架实现一个基于WorkList的活跃变量分析demo。变量在某个程序点有两种状态,live 或 dea…...

Oracle 证书的重要性
随着信息技术的飞速发展,数据库管理已成为企业运营中不可或缺的一部分。Oracle作为全球领先的数据库管理系统提供商,其Oracle Certified Professional(OCP)认证已成为数据库管理员和开发人员追求的专业认证之一。本文将深入探讨Or…...

【Go专家编程——并发控制——Mutex】
1.Mutex 互斥锁是并发程序中对共享资源进行访问控制的主要手段,对此Go语言提供了Mutex,对外暴露Lock()和Unlock两个方法,分别用于加锁和解锁。 1.1 Mutex的数据结构 源码如下: type Mutex struct{state int32//代表互斥锁的状…...

SRE视角下的DevOps构建之道
引言: 随着数字化时代的飞速发展,软件成为了企业竞争力的核心。为了更高效地交付高质量的软件,DevOps(Development和Operations的组合)作为一种文化、实践和工具集的集合,逐渐成为了行业内的热门话题。然而…...

小白如何如何理解滑动窗口最大值问题python
文章目录 题目描述思路什么时候弹出元素什么时候加入元素 代码示例和解释 题目描述 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 举例: 输…...

Linux--进程间通信(2)(有名管道)
目录 1.原理 2.创建命名管道 3.使用命名通道实现简单的通信 4.使用创建的命名管道 1.原理 匿名管道没有名称,它们是通过句柄在父进程和子进程之间传递的。这意味着匿名管道只能用于具有父子关系的进程之间。 但如果程序之间没关系,那么这时候就要用…...

window自动启动bat文件
开机自动开启远程桌面, WinR 执行netplwiz 命令进入设置;取消勾选,可选择所需用户,点击应用,输入远程的密码即可 开机自动开启远程桌面, WinR 执行netplwiz 命令进入设置;取消勾选࿰…...

2024年蓝桥杯Web开发【大赛大纲】15届
一、 组别 Web应用开发分为:大学组和职业院校组。 每位选手只能申请参加其中一个组别的竞赛。各个组别单独评奖。 研究生和本科生只能报大学组。 其它高职高专院校可自行选择报任意组别。 二. 竞赛赛程 省赛时长:4小时。 决赛时长:4小…...

【vue-cli搭建vue项目的过程2.x】
vue-cli搭建vue项目 vue-cli搭建vue项目安装node安装vue-cli脚手架并创建项目安装 Ant Design Vue或element-ui(笔者使用Ant-design-vue组件,并全局引入)开发安装三方库包1、Package.json文件---引入如下package.json文件执行npm i或npm install命令即可下载如下依赖…...

Android 生成正式版密钥库 KeyStore
步骤1:打开生成正式版密钥库设置 点击 Build 菜单,选择 Generate Signed App Bundle or APK: 这是打开后的样子: 步骤2:选择 APK Android App Bundle 是用于上架 Google Play 商店的。 正常情况下选择 APK。 选择…...

POLARDB:新零售用户MySQL上云最佳选择
什么是云数据库POLARDB? POLARDB是阿里云自主研发的最新一代RDS关系型数据库,是特别针对互联网场景设计的Cloud-Native 云原生数据库。POLARDB for MySQL版本,在提供100%兼容MySQL5.6/8.0的关系型事务处理ACID特性之上,能够提供完…...

PHP MySQL图解学习指南:开启Web开发新篇章
PHP曾经是最流行的Web开发语言,许多世界领先的网站(如Facebook、维基百科和WordPress)都是用它编写的。PHP运行在Web服务器端,通过使用存储在MySQL数据库中的数据,使得网站可以为每一位访问者显示不同的定制页面。书中采用简单、直观的图示化…...

uniapp一些问题解决
1.按钮边框如何去除? 参考博主:微信小程序按钮去不掉边框_微信小程序button去掉边框-CSDN博客文章浏览阅读1k次。最近在学uni-app,顺便自己写个小程序。左上角放了个button,可边框怎么也去不掉…原来微信小程序的按钮要去掉边框要…...

数字经济讲师培训师教授唐兴通谈新质生产力数字化转型高质量发展AI人工智能大模型大数据经信委大数据管理局
什么是数字经济? 数字经济是指通过数字技术将个人、企业、设备、数据和运营连接起来而产生的经济活动。它涵盖了互联网、移动技术、大数据和信息通信技术等多个行业和技术之间的在线连接和交易。 数字经济不同于传统经济,因为它依赖数字技术、在线交易…...

关于APM32F407配置串口DMA收发没有数据的问题记录
一.问题环境 整活了一套APM32F407的板子,用了APM32F4xx_SDK_V1.4的标准外设库,正在搭建移植底层BSP框架串口部分,BSP底层配置逻辑是从STM32F407移植过来的。DMA发送时才使能通道及配置外设地址及缓存大小。 串口1DMA配置过程如下&…...

基于python实现的深度学习web多格式纠错系统
基于python实现的深度学习web多格式纠错系统 开发语言:Python 数据库:MySQL所用到的知识:Django框架工具:pycharm、Navicat、Maven 系统功能实现 用户登录 登录功能是本系统一个非常重要的功能,这极大的保护了系统的安全。登录…...

UE5文件操作
首先在虚幻引擎中创建UMyBlueprintFunctionLibrary类,可以在该类中写我们重复利用的功能,并且这些功能不依赖于特定的游戏对象,方便全局调用。 1.文件的读取和写入 UFUNCTION(BlueprintCallable, Category "File")static bool lo…...

element plus 去掉select选择框的边框,并修改右侧图标
1.去掉选择框边框 ::v-deep .el-select__wrapper{ box-shadow: none; } ::v-deep .is-hovering{ box-shadow: none !important; }2.修改选择框右侧图标 新建CaretBottom.vue文件内容: <template><el-icon><CaretBottom /></el-icon> <…...

Ceph KernelFuse GetSet Quota
Kernel fuse set示例...

JVM学习-字节码指令集(二)
对象的创建与访问指令 创建指令 虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建和操作使用了不同的字节码指令创建类实例指令:new 它接收一个操作数,指向常量池的索引,表示要创建的类型,执行完成后&am…...

解密网络流量监控:优化IT运维的利器
引言: 在当今数字化时代,网络流量监控是维护网络稳定与业务连续性的关键。作为一名资深网络工程师,我将分享一些关于网络流量监控的重要知识,并探讨如何在IT运维中运用这一工具优化网络性能,确保业务的顺畅进行。 1. 网…...

oracle 分区表常用语句(2)
给分区表增加分区 第一种不存在MAXVALUE(直接添加即可) ALTER TABLE T6 ADD PARTITION P5 VALUES LESS THAN(TO_DATE( 2018-08-01 00:00:00, SYYYY-MM-DD HH24:MI:SS, NLS_CALENDARGREGORIAN));第二种存在MAXVALUE alter table T6 split PARTITION P4 at(TO_DAT…...

Python函数式编程进阶:用函数实现设计模式
文章目录 函数式编程进阶:用函数实现设计模式案例实现:构建“策略”模式使用函数实现”策略“模式享元 选择最佳策略:简单的方式 globals关键字 函数式编程进阶:用函数实现设计模式 案例实现:构建“策略”模式 策略模…...

Ingress controller:Kubernetes 的瑞士军刀
原文作者:Brian Ehlert of F5 原文链接:Ingress controller:Kubernetes 的瑞士军刀 转载来源:NGINX 中文官网 NGINX 唯一中文官方社区 ,尽在 nginx.org.cn 许多人认为 Ingress controller(Ingress 控制器&…...

uniapp tabBar app页面滚动闪屏的问题
我在做app的时候,调试tabBar页面滚动时莫名其妙的闪屏,其他页面不闪屏,可能跟新建的项目样式有关。 修改方法如下。 在pages.json中 "tabBar": {"selectedColor": "#204AFF","color": "#ccc…...

【计算机毕业设计】388微信小程序足球赛事及队伍管理系统
🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板ÿ…...

监控易监测对象及指标之:华为FusionInsight Kafka服务全方位监控
监控易提供对华为FusionInsight Kafka服务的全方位监控功能。该功能可以帮助用户实时监测Kafka服务的各项性能指标,确保服务的稳定运行和高效性能。 具体功能点包括: 服务状态监测:监控易能够实时监测华为FusionInsight Kafka服务的运行状态…...

Python装饰器的应用
Python 中的装饰器是一种语法糖,可以在运行时,动态的给函数或类添加功能。装饰器本质上是一个函数,使用 函数名就是可实现绑定给函数的第二个功能 。它的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。 …...

【数据结构与算法 | 基础篇】力扣232, 225
1. 力扣232 : 用栈实现队列 (1). 题 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): 实现 MyQueue 类: void push(int x) 将元素 x 推到队列的末尾int pop() 从队列的开头移…...

内网(极空间)搭建gitlab跳板机转发端口及域名配置
背景说明 https://blog.csdn.net/GodDavide/article/details/139182475 上文说到: 我已经用docker搭好了gitlab-ce服务,但我是部署在自己的家庭nas-极空间z4pro里的,属于内网环境。 另外我有一台阿里云服务器,做跳板机。 我有一个阿里的域名…...