使用 C++23 从零实现 RISC-V 模拟器(1):最简CPU

本节实现一个最简的 CPU ,最终能够解析 addaddi 两个指令。如果对计算机组成原理已经有所了解可以跳过下面的内容直接看代码实现。

完整代码在这个分支:lab1-cpu-add,本章节尾有运行的具体指令。

1. 冯诺依曼结构

冯·诺依曼结构是现代计算机体系结构的基础,由约翰·冯·诺依曼在 1945 年提出。这种结构也称为冯·诺依曼体系结构,其核心特点是将程序指令和数据存储在同一个读写存储器(内存)中,计算机的工作流程则是按顺序执行存储器中的指令。这一概念是区别于早期的计算机设计,如图灵机和哈佛架构,后者将数据存储和指令存储分开。

冯·诺依曼结构的五大组成部分:

  1. 中央处理单元(CPU):负责解释计算机程序中的指令和处理数据。
  2. 存储器:存储程序和数据。在冯·诺依曼结构中,程序指令和数据共享同一存储区。
  3. 输入设备:用于将数据和程序输入到计算机中。
  4. 输出设备:将处理结果展示给用户。
  5. 控制单元(CU):协调其他部件的操作。

工作流程:

  1. 指令和数据的存储:在开始执行程序之前,程序的指令和所需的数据被加载到存储器中。
  2. 指令的执行:CPU 从存储器中读取指令,解析指令,然后执行指令。这个过程包括从存储器读取数据、在算术逻辑单元(ALU)中进行计算,以及将结果写回存储器或通过输出设备展示出来。
+------------------+      +-------------+
|    输入设备       | ---> |             |
+------------------+      |             ||             |
+------------------+      |             |      +-----------------+
|    存储器         | <--> |     CPU     | ---> |    输出设备      |
+------------------+      |             |      +-----------------+|             |
+------------------+      |             |
|    控制单元       | <--> |             |
+------------------+      +-------------+

在这个结构中,控制单元指导操作的流程,确保指令正确执行。输入设备可以是键盘、鼠标等,它们把用户的输入转换成机器可以理解的数据。输出设备如显示器和打印机,用于向用户展示结果。存储器不仅保存了待处理的数据,还保存了计算机程序的所有指令。CPU 是核心部件,执行所有的计算和逻辑处理。

重要性:

冯·诺依曼结构的提出,极大推动了计算机科学的发展,使得计算机设计变得更加灵活,程序存储成为可能。在这种架构下,更改程序不再需要重新设计计算机硬件,仅需在存储器中替换或修改程序即可。这一概念至今仍是大多数计算机设计的基础。

2. 最简 CPU

用数组来模拟内存,其中存放待执行的指令。pc 是程序计数器(Program Counter)的简写,用来指向当前正在执行指令的下一条指令。此外 RISC-V 有 32 个寄存器,可以用数组来存放,寄存器用来存放临时产生数据。

// main.cpp
#include <vector>
#include <array>
#include <cstdint>// 定义DRAM_SIZE为128MB
const uint64_t DRAM_SIZE = 1024 * 1024 * 128;class Cpu {// RISC-V 有 32 个寄存器std::array<uint64_t, 32> regs;// PC 寄存器包含下一条指令的内存地址uint64_t pc;// 内存,一个字节数组。在真实的 CPU 中没有内存,这里仅作模拟。std::vector<uint8_t> dram;public:// 构造函数Cpu(const std::vector<uint8_t>& code) : pc(0), dram(code) {regs.fill(0); // 初始化寄存器为0regs[2] = DRAM_SIZE - 1; // 设置堆栈指针寄存器的初始值}// 可能需要的其他成员函数声明
};int main() {// 示例代码使用std::vector<uint8_t> code = { /* 初始化代码 */ };Cpu cpu(code);// 使用cpu对象进行操作
}

其中 pc 的值置为 0 表示表示程序从地址 0 处开始执行。

2. CPU 流水线

在现代计算机体系结构中,尤其是遵循冯·诺依曼架构的计算机系统,程序的执行可以分解为几个连续的阶段,这些阶段共同构成了 CPU 的指令周期。这些阶段包括取指(Instruction Fetch, IF)、解码(Instruction Decode, ID)、执行(Execute, EX)、访存(Memory Access, MEM)和写回(Write Back, WB)。这个过程是循环进行的,每个阶段完成特定的任务,确保计算机程序顺利执行。

+--------+   +--------+   +--------+   +--------+   +--------+
| 取指IF  |-->| 解码ID |-->| 执行EX  |-->| 访存MEM |-->| 写回WB |
+--------+   +--------+   +--------+   +--------+   +--------+

在这个示意图中,每行代表 CPU 中的一条指令随时间前进经过不同的处理阶段。每个方框代表流水线的一个阶段,箭头表示指令从一个阶段移动到下一个阶段的流程。这种设计使得在任何给定的时钟周期内,最多可以有五条指令处于不同的执行阶段,极大提高了 CPU 的效率和性能。

  • 取指 IF:从内存中读取指令。
    • CPU 使用程序计数器(PC)指向的地址从内存中读取指令。读取后,PC 会更新到下一条指令的地址,为下一个周期准备。
  • 解码 ID:解析指令并准备必要的操作数。
    • 指令解码器(ID)解析取出的指令,确定需要执行的操作和操作数。这可能涉及到从指令中提取立即数、计算地址、确定寄存器编号等。
  • 执行 EX:执行计算或其他操作。
    • 执行阶段根据解码阶段的结果,进行相应的算术逻辑运算(ALU 操作)、分支决策或其他操作。此阶段可能需要使用到 ALU(算术逻辑单元)。
  • 访存 MEM:进行内存访问,如数据加载或存储。
    • 如果指令需要读取内存(如加载操作)或向内存写入数据(如存储操作),这一阶段将完成该操作。这一步是可选的,因为不是所有指令都需要访问内存。
  • 写回 WB:将执行结果写回寄存器。
    • 将执行阶段的结果或访存阶段从内存读取的数据写回到指定的寄存器。这一步确保了指令的执行结果可以被后续指令使用。

这五个阶段共同构成了指令的完整执行周期,是现代 CPU 设计的基础。通过将指令执行分解为这些阶段,计算机能够以高效和有序的方式运行程序。每个阶段都由 CPU 的不同部件负责,使得计算机能够在任何给定时刻执行多条指令的不同阶段,这种设计是流水线处理的基础,极大提高了 CPU 的执行效率。

3. 取指

接下来实现Cpu类中的fetch函数。此函数的目的是从 CPU 内部的动态随机存取存储器(DRAM)中读取当前程序计数器(pc)指向的指令。

文本图形化解释:

假设我们有以下 DRAM 内容和一个pc值指向 DRAM 的起始位置:

DRAM 内容(示例):
+----+----+----+----+
| 01 | 02 | 03 | 04 | ...
+----+----+----+----+↑pc
  • 步骤 1: pc指向 DRAM 中的第一个字节。

  • 步骤 2: fetch函数读取pc指向的四个字节(01, 02, 03, 04)。

  • 步骤 3: 将这四个字节组合成一个 32 位的指令。

组合过程:01                   02                   03                   04
00000001 (字节1) | 00000010 (字节2) << 8 | 00000011 (字节3) << 16 | 00000100 (字节4) << 24
= 04030201 (十六进制)
          DRAM
+----+----+----+----+----+----+----+----+
| 01 | 02 | 03 | 04 | xx | xx | xx | xx | ...
+----+----+----+----+----+----+----+----+↑pc(假设pc=0)fetch操作:
1. 读取 [01] → index=pc
2. 读取 [02] → index=pc+1
3. 读取 [03] → index=pc+2
4. 读取 [04] → index=pc+3组合为32位指令:04030201

代码解释:

// main.cpp
class Cpu {// ...
public:// ...// Fetch函数用于读取当前pc指向的指令uint32_t fetch() {size_t index = static_cast<size_t>(pc); // 确保pc值在转换时不会丢失信息uint32_t inst = static_cast<uint32_t>(dram[index])| (static_cast<uint32_t>(dram[index + 1]) << 8)| (static_cast<uint32_t>(dram[index + 2]) << 16)| (static_cast<uint32_t>(dram[index + 3]) << 24);return inst;}
};
  • size_t index = static_cast<size_t>(pc);pc的值转换为适合作为索引的类型。
  • static_cast<uint32_t>(dram[index]) 读取第一个字节,并保持其原位。
  • (static_cast<uint32_t>(dram[index + 1]) << 8) 读取第二个字节,并左移 8 位。
  • (static_cast<uint32_t>(dram[index + 2]) << 16) 读取第三个字节,并左移 16 位。
  • (static_cast<uint32_t>(dram[index + 3]) << 24) 读取第四个字节,并左移 24 位。
  • 这四个部分通过位或操作(|)组合成一个完整的 32 位指令。

这个过程展示了如何从连续的字节中构建一个完整的指令,是执行流水线中取指阶段的关键步骤。

同时上面的实现是小端,如果反过来高位数据位于低地址部分就是大端。

4. 解码

上一部分已经读取到指令了,接下来就是解析指令然后执行。接下来实现如何解析 add 和 addi 指令,在解析之前要先弄清楚这两个指令的具体作用及其使用场景。

add 指令和 addi 指令是在汇编语言和计算机架构中常用的两种基本指令,尤其在 MIPS 架构中广泛应用。它们用于进行加法运算,但在操作方式和使用场景上有所不同。

add 指令

add 指令用于将两个寄存器中的数值相加,并将结果存储在另一个寄存器中。这是一种寄存器到寄存器的操作。

格式: add 目标寄存器, 源寄存器1, 源寄存器2

例子:

add $t0, $t1, $t2

这条指令的意思是将 $t1$t2 中的值相加,然后将结果存储在 $t0 中。

addi 指令

addi 指令是 “add immediate” 的缩写,它将一个寄存器中的值与一个立即数(即直接提供的数值)相加,并将结果存储在另一个寄存器中。这是一种寄存器到立即数的操作。

格式: addi 目标寄存器, 源寄存器, 立即数

例子:

addi $t0, $t1, 5

这条指令的意思是将 $t1 中的值与立即数 5 相加,然后将结果存储在 $t0 中。

使用场景

  • add 指令 通常用于需要将两个变量的值相加的情况,这两个变量的值在执行指令之前已经被加载到寄存器中。
  • addi 指令 常用于需要将某个变量的值与一个已知的常数相加的场景,例如数组索引计算、根据偏移量计算地址等。

为了以文本图形化的方式更直观地展示这两个指令的作用,我们可以用简化的图表来表示它们的操作流程:

  1. add 操作流程
   [寄存器1]     [寄存器2]|             ||             |+----加法----+|↓[目标寄存器]
  1. addi 操作流程
   [寄存器]     [立即数]|           ||           |+----加法----+|↓[目标寄存器]

通过上面的解释和图形化表示,可以看出 addaddi 指令在汇编语言编程中如何用于处理不同的加法运算场景。

指令内部组成

上部分已经讲解了 add 和 addi 指令的具体功能和使用场景,接下来讲解这两个指令是如何存放在内存中的。

add 指令格式

add 指令在 RISC-V 中是一种 R 型(寄存器-寄存器)指令,用于将两个寄存器的数值相加,并将结果存储在第三个寄存器中。

  • 操作码(opcode):标识这是一种什么操作的字段,对于 add 指令,操作码指定了这是一种算术操作。
  • 目标寄存器(rd):存放操作结果的寄存器。
  • 功能码(funct3):提供操作的进一步细化,对于 add 来说,这个字段有特定的值。
  • 源寄存器 1(rs1):第一个操作数的寄存器。
  • 源寄存器 2(rs2):第二个操作数的寄存器。
  • 功能码(funct7):与 funct3 合作,确定是哪种具体的算术操作,add 有其特定的值。
┌───────┬─────┬──────┬─────┬─────┬────────┐
│opcode │  rd │funct3│ rs1 │ rs2 │ funct7 │
└───────┴─────┴──────┴─────┴─────┴────────┘7 bits  5 bits 3 bits 5 bits 5 bits  7 bits

接下来结合具体的汇编代码来讲解:

  1. 对于 add x7, x5, x6 指令:将寄存器 x5 和寄存器 x6 的值相加,并将结果存储到寄存器 x7 中。

下面是对应在内存中的表示:

┌────────┬──────┬──────┬──────┬──────┬────────┐
│opcode  │  rd  │funct3│ rs1  │ rs2  │ funct7 │
│ 0110011│ 00111│ 000  │ 00101│ 00110│ 0000000│
└────────┴──────┴──────┴──────┴──────┴────────┘7 bits  5 bits 3 bits 5 bits 5 bits  7 bits
  • 操作码(opcode)0110011,表示这是一个 R-type 指令。
  • 目标寄存器(rd)00111,即寄存器 x7
  • 功能码(funct3)000,与 funct7 一起确定这是一个加法操作。
  • 源寄存器 1(rs1)00101,即寄存器 x5
  • 源寄存器 2(rs2)00110,即寄存器 x6
  • 功能码(funct7)0000000,与 funct3 一起指定了这是一个 add 操作。
RISC-V addi 指令格式

addi 指令在 RISC-V 中是一种 I 型(立即数)指令,它将一个寄存器中的数值与一个立即数相加,并将结果存储在另一个寄存器中。

  • 操作码(opcode):标识操作类型的字段,对于 addi,这指定了是一种立即数加法操作。
  • 目标寄存器(rd):存放操作结果的寄存器。
  • 功能码(funct3):进一步指定了操作的类型,addi 有其特定的值。
  • 源寄存器(rs1):操作数的寄存器。
  • 立即数(imm):与源寄存器中的值相加的立即数。
┌───────┬─────┬─────┬─────┬─────────────────┐
│opcode │  rd │funct3│ rs1 │       imm       │
└───────┴─────┴─────┴─────┴─────────────────┘7 bits  5 bits 3 bits 5 bits      12 bits

接下来结合具体的汇编代码来讲解:

对于 addi x7, x5, 10 指令: 将寄存器 x5 的值与立即数 10 相加,并将结果存储到寄存器 x7 中。

下面是对应在内存中的表示:

┌────────┬──────┬──────┬──────┬─────────────────┐
│opcode  │  rd  │funct3│ rs1  │       imm       │
│ 0010011│ 00111│ 000  │ 00101│ 0000000000101010│
└────────┴──────┴──────┴──────┴─────────────────┘7 bits  5 bits 3 bits 5 bits      12 bits
  • 操作码(opcode)0010011,表示这是一个 I-type 指令。
  • 目标寄存器(rd)00111,即寄存器 x7
  • 功能码(funct3)000,确定这是一个立即数加法操作。
  • 源寄存器(rs1)00101,即寄存器 x5
  • 立即数(imm)0000000000101010,表示十进制数 10。这里立即数字段实际上是 12 位,为简化表示,应解释为补码形式,代表正数 10

通过上面的例子,我们可以清楚地看到 RISC-V 架构下 addaddi 指令的内部组成及其编码方式。这种表示不仅有助于理解指令的结构,也方便在设计汇编语言程序时进行指令选择和使用。

使用场景和解析

  • add 指令 用于两个寄存器值的加法运算,常用于各种数值计算和数据处理任务。
  • addi 指令 用于将寄存器值与立即数相加,常见于地址计算、数值调整等场景。

这些指令的设计反映了 RISC-V 指令集的目标,即提供简单、高效且足够灵活的指令集,以支持现代编译器技术和硬件实现的需求。

代码

// main.cpp
class Cpu {
public:// 其他成员和方法...// 执行指令的函数void execute(uint32_t inst) {// 解析指令中的操作码(opcode),占用最低的7位uint32_t opcode = inst & 0x7f;// 解析目标寄存器(rd),位于指令的第7到11位uint32_t rd = (inst >> 7) & 0x1f;// 解析第一个源寄存器(rs1),位于指令的第15到19位uint32_t rs1 = (inst >> 15) & 0x1f;// 解析第二个源寄存器(rs2),位于指令的第20到24位uint32_t rs2 = (inst >> 20) & 0x1f;// 解析功能码(funct3),位于指令的第12到14位uint32_t funct3 = (inst >> 12) & 0x7;// 解析功能码(funct7),位于指令的第25到31位uint32_t funct7 = (inst >> 25) & 0x7f;// 寄存器x0永远为0regs[0] = 0;// 执行阶段switch (opcode) {case 0x13: { // 处理addi指令// 解析立即数,将指令的最高20位视为符号扩展的立即数int64_t imm = static_cast<int32_t>(inst & 0xfff00000) >> 20;// 执行加法操作,将rs1寄存器的值与立即数相加,并将结果存入rd寄存器regs[rd] = regs[rs1] + imm;break;}case 0x33: { // 处理add指令// 执行加法操作,将rs1和rs2寄存器的值相加,并将结果存入rd寄存器regs[rd] = regs[rs1] + regs[rs2];break;}default:// 如果操作码不是预期中的值,则输出错误信息std::cerr << "Invalid opcode: " << std::hex << opcode << std::endl;break;}}// 其他成员变量和方法...
};

5. 测试

上面已经上一个最简 CPU 了,接下来需要增加一些辅助功能来使得 CPU 跑起来。

首先是需要能够查看寄存器中的数据,根据数据变化来验证指令执行正确。

class Cpu {
public:// 其他成员和方法...// RISC-V 寄存器名称const std::array<std::string, 32> RVABI = {"zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2","s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5","a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7","s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6",};void dump_registers() {std::cout << std::setw(80) << std::setfill('-') << "" << std::endl; // 打印分隔线std::cout << std::setfill(' '); // 重置填充字符for (size_t i = 0; i < 32; i += 4) {std::cout << std::setw(4) << "x" << i << "(" << RVABI[i] << ") = " << std::hex << std::setw(16) << std::setfill('0') << regs[i] << " "<< std::setw(4) << "x" << i + 1 << "(" << RVABI[i + 1] << ") = " << std::setw(16) << regs[i + 1] << " "<< std::setw(4) << "x" << i + 2 << "(" << RVABI[i + 2] << ") = " << std::setw(16) << regs[i + 2] << " "<< std::setw(4) << "x" << i + 3 << "(" << RVABI[i + 3] << ") = " << std::setw(16) << regs[i + 3] << std::endl;}}
};

总的来说上面的代码就是为 32 个寄存器增加了对应的名称以及提供一个一个能够打印其中数值的方法。

创建 add-addi.s 并写入下面的内容

.global _start
_start:addi x29, x0, 5addi x30, x0, 37add x31, x30, x29

在汇编语言中,.global _start_start: 的语句定义了程序的入口点。

  • .global _start:这条指令告诉链接器(linker),_start 标签是一个全局符号,可以被程序的其他部分或其他链接的文件访问。更重要的是,它标示 _start 为程序的入口点,即程序执行的起始位置。这对于操作系统(OS)来说非常关键,因为在程序被加载到内存并执行时,操作系统需要知道从哪里开始执行程序。

  • _start::这是一个标签,紧跟在它后面的是程序的入口点。在这个位置上编写的指令将会是程序执行的第一批指令。在一个裸机(bare-metal)环境或操作系统内核开发中,_start 是执行流程的起点,没有标准库或运行时环境的初始化过程。

综上所述,.global _start_start: 一起定义了程序开始执行的地方,为操作系统提供了一个明确的起点来运行程序。如果不写的话会报错。

这段代码是使用 RISC-V 汇编语言编写的,它执行了一个非常简单的任务:计算两个数值的和,并将结果存储。具体来说,代码做了以下几件事情:

  1. 将数字 5 存储到寄存器 x29。
  2. 将数字 37 存储到寄存器 x30。
  3. 将寄存器 x29 和 x30 中的数值相加,将结果存储到寄存器 x31。

总结来说,这段代码简单地计算了 5 和 37 的和,然后将结果 42 存储到寄存器 x31 中。

将汇编转为二进制文件:

$ riscv64-unknown-elf-gcc -Wl,-Ttext=0x0 -nostdlib -o add-addi add-addi.s
$ riscv64-unknown-elf-objcopy -O binary add-addi add-addi.bin

运行并测试是否正确:

mkdir -p build && cd build && cmake .. && make && ./crvemu ../add-addi.bin
~/crvemu/build$ ./crvemu ../add-addi.bin
--------------------------------------------------------------------------------
x0(zero) = 0000000000000000 000x1(ra) = 0000000000000000 000x2(sp) = 0000000007ffffff 000x3(gp) = 0000000000000000
000x4(tp) = 0000000000000000 000x5(t0) = 0000000000000000 000x6(t1) = 0000000000000000 000x7(t2) = 0000000000000000
000x8(s0) = 0000000000000000 000x9(s1) = 0000000000000000 000xa(a0) = 0000000000000000 000xb(a1) = 0000000000000000
000xc(a2) = 0000000000000000 000xd(a3) = 0000000000000000 000xe(a4) = 0000000000000000 000xf(a5) = 0000000000000000
000x10(a6) = 0000000000000000 000x11(a7) = 0000000000000000 000x12(s2) = 0000000000000000 000x13(s3) = 0000000000000000
000x14(s4) = 0000000000000000 000x15(s5) = 0000000000000000 000x16(s6) = 0000000000000000 000x17(s7) = 0000000000000000
000x18(s8) = 0000000000000000 000x19(s9) = 0000000000000000 000x1a(s10) = 0000000000000000 000x1b(s11) = 0000000000000000
000x1c(t3) = 0000000000000000 000x1d(t4) = 0000000000000005 000x1e(t5) = 0000000000000025 000x1f(t6) = 000000000000002a

注意最后一行最后三个,因为是二进制,所以数据是正确的,例如 25 对应的十进制就是 37 。

至此本节内容已经完成,目前已经能够实现解析 add 和 addi 两个指令。

👉🏻 文章汇总「从零实现模拟器、操作系统、数据库、编译器…」:https://okaitserrj.feishu.cn/docx/R4tCdkEbsoFGnuxbho4cgW2Yntc

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/679451.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么把视频音乐提取成mp3?分享详细工具和方法!

在数字媒体时代&#xff0c;音乐已经成为我们生活中不可或缺的一部分。有时候&#xff0c;我们会在社交媒体、视频分享网站或在线视频平台上看到一些非常喜欢的视频音乐&#xff0c;想要将其保存为MP3格式以便随时随地聆听。那么&#xff0c;如何从视频中提取音乐并转换为MP3格…

python+flask+django医院预约挂号系统6nrhh

医院预约挂号系统主要有管理员、用户和医生三个功能模块。以下将对这三个功能的作用进行详细的剖析。 技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django/flask Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具…

【Spring框架】Spring事务同步

目录 一、什么是Spring事务同步 二、 事务同步管理器 2.1 TransactionSynchronizationManager事务同步管理器 2.1.1 资源同步 2.1.2 事务同步 2.1.3 总结 三、事务同步管理器保障事务的原理 四、spring事务为何使用TransactionSynchronizationManager spring源码实现 …

力扣_字符串5—解码方法

题目 一条包含字母 A-Z 的消息通过以下映射进行了 编码 &#xff1a; ‘A’ -> “1”‘B’ -> “2”…‘Z’ -> “26” 要 解码 已编码的消息&#xff0c;所有数字必须基于上述映射的方法&#xff0c;反向映射回字母&#xff08;可能有多种方法&#xff09;。例如&…

第二十七天| 39. 组合总和 、40.组合总和II、131.分割回文串

Leetcode 39. 组合总和 题目链接&#xff1a;39 组合总和 题干&#xff1a;给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序…

【网络】:序列化和反序列化

序列化和反序列化 一.json库 二.简单使用json库 前面已经讲过TCP和UDP&#xff0c;也写过代码能够进行双方的通信了&#xff0c;那么有没有可能这种通信是不安全的呢&#xff1f;如果直接通信&#xff0c;可能会被底层捕捉&#xff1b;可能由于网络问题&#xff0c;一方只接收到…

JavaScript中的for循环和map方法

JavaScript中的for循环和map方法 在JavaScript中&#xff0c;循环是一种常见的编程技巧&#xff0c;用于重复执行一段代码。for循环和map方法都可以用于循环操作&#xff0c;但它们在语法和应用场景上存在一些区别。本文将详细讲解JavaScript中的for循环和map方法&#xff0c;以…

【Linux技术宝典】Linux入门:揭开Linux的神秘面纱

文章目录 官网Linux 环境的搭建方式一、什么是Linux&#xff1f;二、Linux的起源与发展三、Linux的核心组件四、Linux企业应用现状五、Linux的发行版本六、为什么选择Linux&#xff1f;七、总结 Linux&#xff0c;一个在全球范围内广泛应用的开源操作系统&#xff0c;近年来越来…

c++中的模板(2)-- 函数模板(及模板函数)

目录 函数模板: 语法: 分析: 函数调用: 怎么实现: 模板函数: 函数模板: 其实就是在函数中使用模板&#xff0c;我们前面的swap函数就是一个函数模板。 语法: template <typename 类型名1,typename 类型名2 ...> template <typename T> void Sw…

【MySQL】-17 MySQL综合-3(MySQL创建数据库+MySQL查看数据库+MySQL修改数据库+MySQL删除数据库+MySQL选择数据库)

MySQL创建数据库查看数据库修改数据库删除数据库选择数据库 一 MySQL创建数据库实例1&#xff1a;最简单的创建 MySQL 数据库的语句实例2&#xff1a;创建 MySQL 数据库时指定字符集和校对规则 二 MySQL查看或显示数据库实例1&#xff1a;查看所有数据库实例2&#xff1a;创建并…

go常见问题

1、go : 无法将“go”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1 解决方式&#xff1a;$env:Path [System.Environment]::GetEnvironmentVa…

【Network Management】AUTOSAR架构下CanNm User Data详解

目录 前言 正文 1.CanNm user data概念 2.CanNm user data配置 2.1CDD方式访问CanNm user data

神经网络(Nature Network)

最近接触目标检测较多&#xff0c;再此对最基本的神经网络知识进行补充&#xff0c;本博客适合想入门人工智能、其含有线性代数及高等数学基础的人群观看 1.构成 由输入层、隐藏层、输出层、激活函数、损失函数组成。 输入层&#xff1a;接收原始数据隐藏层&#xff1a;进行…

Clickhouse查询语句执行过程

问题 简述clickhosue中一条select语句的执行过程&#xff0c;使用的引擎是ReplacingMergeTree。例如&#xff1a; select col1,col2 from table final prewhere col3 > ? and col4 ? and col5 ? -- col3为分区键&#xff0c;col4为二级索引,col5为主键字段 where col…

Stable Diffusion 模型下载:Disney Pixar Cartoon Type B(迪士尼皮克斯动画片B类)

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十

阿里云服务器带宽计费模式是什么?怎么选择?

阿里云服务器带宽计费模式分为“按固定带宽”和“按使用流量”&#xff0c;有什么区别&#xff1f;按固定带宽是指直接购买多少M带宽&#xff0c;比如1M、5M、10M、100M等&#xff0c;阿里云直接分配用户所购买的带宽值&#xff0c;根据带宽大小先付费再使用&#xff1b;按使用…

Windows Server 2019 搭建并加入域

系列文章目录 目录 系列文章目录 文章目录 前言 一、域是什么&#xff1f; 二、配置服务器 1.实验环境搭建 1)实验服务器配置和客户端 2)实验环境 2.服务器配置 账户是域服务器的账户和密码 文章目录 Windows Server 2003 Web服务器搭建Windows Server 2003 FTP服务器搭…

Hadoop:认识MapReduce

MapReduce是一个用于处理大数据集的编程模型和算法框架。其优势在于能够处理大量的数据&#xff0c;通过并行化来加速计算过程。它适用于那些可以分解为多个独立子任务的计算密集型作业&#xff0c;如文本处理、数据分析和大规模数据集的聚合等。然而&#xff0c;MapReduce也有…

1306. 跳跃游戏 III

经过测试&#xff0c;两种写法耗时差距10倍&#xff0c;我也不知道原因是啥 用访问次数的是更快的 class Solution { public:int n;bool dfs(vector<int>& arr, int start, vector<int>& visited){if(start<0||start>n || visited[start]1) return …

每日OJ题_位运算⑤_力扣371. 两整数之和

目录 力扣371. 两整数之和 解析代码 力扣371. 两整数之和 371. 两整数之和 难度 简单 给你两个整数 a 和 b &#xff0c;不使用 运算符 和 - &#xff0c;计算并返回两整数之和。 示例 1&#xff1a; 输入&#xff1a;a 1, b 2 输出&#xff1a;3示例 2&#xff1a; …