从0到1使用C++实现一个模拟器-1-【实现最简CPU】

news2024/11/17 16:26:01

文章目录

  • uint64_t
  • std
  • std::array
  • CPU和CU
  • 构造函数
  • size_t
  • static_cast
  • std::ifstream
  • riscv64-unknown-elf-objcopy -O binary add-addi add-addi.bin
  • riscv64-unknown-elf-gcc -Wl,-Ttext=0x0 -nostdlib -o add-addi add-addi.s
  • -wl
  • std::hex std::setw() std::setfill()各自的用法
  • hexdump
  • add与addi的risv编码
  • 简单CPU工作流程
  • 实现流程
  • 字节码
  • cpu.cpp

uint64_t

uint64_t是C/C++中的一种无符号整数类型,它表示一个64位的无符号整数。在计算机中,无符号整数是以二进制补码形式存储的,因此它的取值范围是从0到2^64-1(即18446744073709551615)。

uint64_t的全称是unsigned int 64-bit。

在C语言中,uint64_t是一个通过typedef定义的别名,代表了一个无符号的64位整型。这种命名方式是一种规范化的表示,其中"u"代表unsigned(无符号),"int"代表integer(整型),而"64"则指明了这是一个64位的整数类型。使用这些标准的数据类型别名可以提高代码的可读性和可维护性,因为它们明确地指出了数据类型的大小和属性。例如,uint8_t、uint16_t、uint32_t和uint64_t分别表示8位、16位、32位和64位的无符号整型。

这些数据类型都是在C99标准中定义的,它们并不是新的数据类型,而是已有数据类型的别名。这样做的好处是,即使在不同的平台上,这些类型的大小可能会有所不同,使用这些标准别名可以确保代码的一致性和移植性。

std

std是C++标准库的命名空间。

在C++编程语言中,std是一个特殊的命名空间,它包含了C++标准库的所有函数、类和其他模板。这个命名空间是为了区分标准库中的函数和对象与用户自定义的或其他库中的函数和对象。使用std命名空间,可以避免名称冲突,并且提供了一种组织代码的方式。以下是一些关于std命名空间的关键点:

标准库函数和对象:如std::cout用于输出,std::cin用于输入,以及各种数据结构和算法,如std::vector、std::sort等都在std命名空间中定义。
使用方式:在使用标准库中的任何函数或对象时,通常需要在前面加上std::来指明它们是来自std命名空间。例如,std::cout << "Hello, World!"中的std::就是必要的限定词。
简化使用:为了避免每次都要写std::,可以在代码中通过using namespace std;来告诉编译器在接下来的代码中使用std命名空间,这样就不需要重复写std::了。但这在某些情况下可能会导致命名冲突,所以需要谨慎使用。

std::array

std::array是一个固定大小的数组容器,提供了许多内置数组所不具备的功能,同时保持了与原生数组相同的随机访问能力和效率。以下是std::array的一些主要用法:

初始化:可以在声明时直接初始化std::array,也可以使用列表初始化的方式。例如:

std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
std::array<int, 5> arr2{6, 7, 8, 9, 10};

  • 访问元素:可以通过下标操作符[]来访问或修改std::array中的元素,就像使用原生数组一样。

  • 获取大小:std::array提供了一个size()成员函数,可以返回数组的大小(元素个数)。

  • 获取数据指针:可以使用data()成员函数获取指向第一个元素的指针,这对于需要以C风格数组形式传递数据的情况非常有用。

  • 迭代器支持:std::array支持随机访问迭代器,可以使用迭代器遍历数组中的元素。

  • 不支持动态变化:由于std::array的大小是固定的,它不支持添加或删除元素,也不支持改变其大小。

  • 自动推导数组大小:在C++17及以后的版本中,可以利用类模板参数推导特性来简化std::array的声明,如std::array arr = {1,2,3,4,5,6};,但需要注意的是,这种推导方式无法只提供数组元素类型让编译器推导数组长度。

总的来说,std::array是一个结合了原生数组优点并增加了更多功能的安全容器。它在需要固定大小数组且希望有更多安全性和便利性的场景下是一个很好的选择。

CPU和CU

CPU(中央处理单元)和CU(控制单元)是计算机中的核心组件,它们在功能上有所区别。具体如下:

CPU:是计算机的大脑,负责执行程序指令和处理数据。它由多个部件组成,包括算术逻辑单元(ALU)、控制单元(CU)、寄存器等。CPU内部采用复杂的电路来执行各种计算和逻辑操作,同时它还负责处理与内存、输入输出设备之间的数据交换。
CU:是CPU内部的一个部件,全称为Control Unit,中文叫做控制单元。CU的主要功能是发出各种控制命令或微指令,控制整个计算机系统的工作,包括CPU内部的各个部件。CU协调各个部件的工作,确保程序能够按照预定的流程顺利执行。

在C++中,public和class是两个关键字,它们用于定义类的成员的访问权限。

class:用于定义一个类,它表示一个抽象的数据类型,可以包含数据成员(属性)和成员函数(方法)。类的定义通常包括类名、类的属性和方法等。例如:

class MyClass {
    // 类的属性和方法
};

public:用于指定类的成员的访问权限为公共(public),即可以在类的外部直接访问这些成员。在类的定义中,使用public关键字修饰的成员可以被其他代码直接访问和操作。例如:

class MyClass {
    public:
        int x; // 公共属性
        void myFunction(); // 公共方法
};

在上面的例子中,x是一个公共属性,可以在类的外部直接访问和修改;myFunction()是一个公共方法,也可以在类的外部直接调用。

需要注意的是,在C++中,如果没有显式地指定访问权限,默认情况下,类的成员都是私有的(private),只能在类的内部访问和操作。如果需要让类的成员在类的外部可访问,就需要使用public关键字来声明。

构造函数

C++中的构造函数是一种特殊的成员函数,它在创建对象时被自动调用。构造函数的名称与类名相同,没有返回值类型,可以有参数。构造函数的主要作用是对对象进行初始化。

以下是一个简单的C++构造函数示例:

#include <iostream>
using namespace std;

class MyClass {
public:
    // 构造函数
    MyClass() {
        cout << "构造函数被调用" << endl;
    }
};

int main() {
    MyClass obj; // 创建对象,自动调用构造函数
    return 0;
}

在这个示例中,我们定义了一个名为MyClass的类,并在其中定义了一个构造函数。当我们在main函数中创建MyClass的对象obj时,构造函数会自动被调用,输出"构造函数被调用"。

size_t

size_t是C++中的一种无符号整数类型,通常用于表示对象的大小或数组的长度。它的定义取决于具体的编译器和操作系统,但通常足够大以表示任何可能的对象大小。在32位系统中,size_t通常是32位的,而在64位系统中,size_t通常是64位的。

static_cast

static_cast 是 C++ 中的一种类型转换操作符,用于将一个表达式从一种类型转换为另一种类型。它主要用于基本数据类型之间的转换,如 int 转 float、double 转 int 等。

std::ifstream

std::ifstream 是 C++ 标准库中的一个类,用于从文件中读取数据。它继承自 std::istream 类,提供了一些成员函数和操作符,可以方便地从文件中读取文本或二进制数据。

使用 std::ifstream 打开文件时,需要指定文件名和打开模式。其中,文件名可以是相对路径或绝对路径,而打开模式则决定了文件的访问方式。常用的打开模式包括:

std::ios::in:以只读方式打开文件;
std::ios::out:以写入方式打开文件;
std::ios::binary:以二进制方式打开文件;
std::ios::ate:打开文件后立即定位到文件末尾;
std::ios::app:以追加方式打开文件。

riscv64-unknown-elf-objcopy -O binary add-addi add-addi.bin

这个命令的作用是将名为"add-addi"的ELF格式文件转换为二进制格式,并将结果保存为"add-addi.bin"。

具体解释如下:

  • "riscv64-unknown-elf-objcopy"是一个用于处理目标文件的工具,它可以将一个文件从一种格式转换为另一种格式。
  • "-O binary"选项表示要将输入文件转换为二进制格式。
  • "add-addi"是要转换的输入文件名。
  • "add-addi.bin"是转换后的输出文件名。

riscv64-unknown-elf-gcc -Wl,-Ttext=0x0 -nostdlib -o add-addi add-addi.s

这个命令的作用是使用RISC-V 64位编译器将汇编文件"add-addi.s"编译为可执行文件"add-addi"。

具体解释如下:

  • "riscv64-unknown-elf-gcc"是一个用于编译和链接程序的工具,它是针对RISC-V架构的交叉编译器。
  • "-Wl,-Ttext=0x0"选项指定了程序的起始地址为0x0。
  • "-nostdlib"选项表示不链接标准库。
  • “-o add-addi"选项指定了输出文件名为"add-addi”。
  • "add-addi.s"是要编译的汇编源文件名。

-wl

-Wl 是一个常用的编译选项,它用于将命令行参数传递给链接器(linker)。在 GCC 编译器中,-Wl 后面通常跟着的是链接器的选项,这些选项可以影响链接过程和生成的可执行文件。

例如,-Wl,-Ttext=0x0 就是一个使用 -Wl 传递的链接器选项,其中 -Ttext=0x0 是链接器的一个具体选项,它告诉链接器将程序的文本段(通常包含程序的代码)的起始地址设置为 0x0。

其他常见的 -Wl 选项包括:

  • -Wl,–start-group 和 -Wl,–end-group:这两个选项用于在链接时指定一个输入文件的顺序,确保这些文件按照指定的顺序被链接。
  • -Wl,-Map:这个选项告诉链接器生成一个映射文件,该文件描述了输入对象和可执行文件之间的映射关系。
  • Wl,-rpath:这个选项用于设置运行时库搜索路径。

在使用 -Wl 选项时,通常需要确保链接器理解并支持你传递的选项。不同的目标平台和操作系统可能会有不同的链接器选项。

std::hex std::setw() std::setfill()各自的用法

std::hex、std::setw() 和 std::setfill() 是 C++ 标准库中的流操作符,用于格式化输出。

std::hex:将后面输出的数据都以十六进制形式显示。它通常与 std::cout 一起使用,例如:

#include <iostream>

int main() {
    int num = 255;
    std::cout << std::hex << num<<123; // 输出 "ff7b"
    return 0;
}

std::setw():设置下一次输出字段的宽度。它通常与 std::left 或 std::right 一起使用来指定对齐方式,但如果没有指定对齐方式,默认是右对齐。例如:

#include <iostream>

int main() {
    int num = 42;
    std::cout << std::setw(5) << num; // 输出 "   42"
    return 0;
}

std::setfill():设置填充字符。它通常与 std::setw() 一起使用,用于在输出字段不足指定宽度时用指定的字符进行填充。例如:

#include <iostream>

int main() {
    int num = 42;
    std::cout << std::setw(5) << std::setfill('0') << num; // 输出 "00042"
    return 0;
}

hexdump

在这里插入图片描述

add与addi的risv编码

在这里插入图片描述
在 RISC-V 中,指令编码格式分为几种类型,其中 addi 指令使用 I-type 格式,而 add 指令使用 R-type 格式。每种类型的指令机器码是32位宽,而且每一位都有特定的作用。

addi (Add Immediate) 指令的 I-type 格式: RISC-V 的 I-type 指令格式如下:

┌───────┬───────┬───────┬─────────────┬───────┬───────┐
│ funct7 │  rs2  │  rs1  │    funct3   │   rd  │ opcode│
├───────┼───────┼───────┼─────────────┼───────┼───────┤
│    立即数 (12 bits)rs1 (5 bits)  │  funct3 |   rd (5 bits)opcode (7 bits) │
└────────────────────────┴─────────────┴────────┴─────────────┘ 

opcode (7 bits): 操作码,对于 addi 是 0010011。
rd (5 bits): 目标寄存器的编号。
funct3 (3 bits): 功能码,对于 addi 是 000。
rs1 (5 bits): 源寄存器1的编号。
立即数 (12 bits): 要加的立即数,它是有符号的,并且在执行时会自动进行符号扩展到32位或更多。
例如,对于指令 addi x5, x10, 15,其二进制编码可能如下所示(只展示了操作相关的二进制位,不包括具体的立即数和寄存器编号):

00000000000011110110000010010011

add (Add) 指令的 R-type 格式: RISC-V 的 R-type 指令格式如下:

┌───────┬───────┬───────┬─────────────┬───────┬───────┐
│ funct7 │  rs2  │  rs1  │    funct3   │   rd  │ opcode│
├───────┼───────┼───────┼─────────────┼───────┼───────┤
│ funct7 (7 bits)rs2 (5 bits)rs1 (5 bits)funct3 (3 bits)rd (5 bits)opcode (7 bits)│
└───────┴───────┴───────┴─────────────┴───────┴───────┘

opcode (7 bits): 操作码,对于 add 是 0110011。
rd (5 bits): 目标寄存器的编号。
funct3 (3 bits): 功能码,对于 add 是 000。
rs1 (5 bits): 源寄存器1的编号。
rs2 (5 bits): 源寄存器2的编号。
funct7 (7 bits): 对于 add,这个字段是 0000000。
例如,对于指令 add x5, x10, x20,其二进制编码可能如下所示(只展示了操作相关的二进制位):

00000001010010100000010110110011

简单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 的执行效率。

实现流程

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

.global _start
_start:
    addi x29, x0, 100
    addi x30, x0, 200
    add x31, x30, x29

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

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

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

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

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

  • 将数字 100存储到寄存器 x29。
  • 将数字 200 存储到寄存器 x30。
  • 将寄存器 x29 和 x30 中的数值相加,将结果存储到寄存器 x31。

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

将汇编转为二进制文件:

其中addi指令可能会被优化位li指令,此时将编译优化关闭,然后故意设置一些特定的值使得没有优化即可
在这里插入图片描述

字节码

地址低处才是opcode
在这里插入图片描述

在这里插入图片描述

        uint32_t code=static_cast<uint32_t>(recv_dram_addr[pc])
            |static_cast<uint32_t>(recv_dram_addr[pc+1]<<8)
            |static_cast<uint32_t>(recv_dram_addr[pc+2]<<16)
            |static_cast<uint32_t>(recv_dram_addr[pc+3]<<24);
            
        uint32_t opcode=(code)&0x7f;
        uint32_t rd=(code>>7)&0x1f;
        uint32_t funct3=(code>>12)&0x3;
        uint32_t rs1=(code>>15)&0x1f;
        uint32_t rs2=(code>>20)&0x1f;
        uint32_t funct7=(code>>25)&0x7f;
        uint32_t imm=(code>>20)&0xfff;

cpu.cpp

#include<bits/stdc++.h>

class DRAM
{   
    public:

    std::vector<uint8_t>code;

    public:
    
    DRAM(std::vector<uint8_t> paranment)
    {
        code=paranment;
    }
    
};

class CPU    //CPU 从存储器中读取指令,解析指令,然后执行指令
{
    std::array<uint64_t,32> registers={0};
    
    uint64_t pc;
    
    uint32_t ir;

    std::vector<uint8_t> recv_dram_addr;

    public:
    CPU(std::vector<uint8_t>positon)
    {
        pc=0;
        ir=0;
        recv_dram_addr=positon;
    }

    uint32_t get_code()
    {
        
        uint32_t code=static_cast<uint32_t>(recv_dram_addr[pc])
            |static_cast<uint32_t>(recv_dram_addr[pc+1]<<8)
            |static_cast<uint32_t>(recv_dram_addr[pc+2]<<16)
            |static_cast<uint32_t>(recv_dram_addr[pc+3]<<24);
        pc=pc+4;
        return code; //little-endian
    } 
    
    void decode_execute(uint32_t code)
    {
        uint32_t opcode=(code)&0x7f;
        uint32_t rd=(code>>7)&0x1f;
        uint32_t funct3=(code>>12)&0x3;
        uint32_t rs1=(code>>15)&0x1f;
        uint32_t rs2=(code>>20)&0x1f;
        uint32_t funct7=(code>>25)&0x7f;
        uint32_t imm=(code>>20)&0xfff;

        switch(opcode)
        {
            case 0x33: //add
            {
                registers[rd]=registers[rs1]+registers[rs2];
                break;
            }
            case 0x13: //addi
            {
                registers[rd]=registers[rs1]+imm;
                break;
            }
            default:
            {
                std::cout<<"invalid opcode";
                break;
            }
        }
    }

    const std::array<std::string,32>registers_name=
    {
        "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 show_registers()
    {
        for(size_t i=0;i<32;i++)
        {
            std::cout<<registers[i];
        }
        
        for(size_t i=0;i<32;i+=4)
        {   
            std::cout<<std::hex
            <<" x"<<std::setw(1)<<std::setfill('0')<<i<<"("<<registers_name[i]<<") "<<registers[i]
            <<" x"<<std::setw(1)<<std::setfill('0')<<i+1<<"("<<registers_name[i+1]<<") "<<registers[i+1]
            <<" x"<<std::setw(1)<<std::setfill('0')<<i+2<<"("<<registers_name[i+2]<<") "<<registers[i+2]
            <<" x"<<std::setw(1)<<std::setfill('0')<<i+3<<"("<<registers_name[i+3]<<") "<<registers[i+3]
            <<std::endl;
        }
    }

    void run()
    {
        while(pc<recv_dram_addr.size())
        {
            uint32_t code=get_code();
            decode_execute(code);
        }
    }

};

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        std::cout<<"please use like \n"<<"./nowfilename <filename>";
        return 0;
    }

    std::ifstream file(argv[1],std::ios::binary);

    std::vector<uint8_t> code(std::istreambuf_iterator<char>(file), {});
    std::cout<<code.size();
    DRAM dram(code);

    CPU cpu(dram.code);
    
    if(!file)
    {
        std::cout<<"can't open "<<argv[1]<<std::endl;
        return 0;
    }

    cpu.run();

    std::cout<<std::setw(50)<<std::setfill('~')<<"\n"<<std::endl;
    cpu.show_registers();
    
    return 0;
}

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

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

相关文章

upload-Labs靶场“1-5”关通关教程

君衍. 一、环境搭建二、第一关 前端JS检测后缀1、源码分析2、禁用浏览器JS上传3、burp抓包修改 三、第二关 MIME头验证1、源码分析2、burp抓包绕过 四、第三关 PHP3绕过1、源码分析2、PHP3绕过 五、第四关 .htaccess重写绕过1、源码分析2、.htaccess复写 六、第五关 黑名单大小…

Qt槽函数不响应的原因总结

Qt专栏&#xff1a;http://t.csdnimg.cn/LE2Lx 目录 1.问题 2.原因 2.1.没有继承QObject&#xff0c;声明Q_OBJECT宏 2.2.信号槽参数不匹配 2.3.信号函数未声明为 signals 2.4.访问权限 2.5.注意connect的位置&#xff0c;信号在创建信号槽连接前使用&#xff0c;则无法…

C#使用iText7将多个PDF文档合并为单个文档

使用HtmlAgilityPack抓取并分析网页内容&#xff0c;然后再调用PuppeteerSharp将网页生成PDF文件&#xff0c;最终的成果如下图所示&#xff0c;得到将近120个pdf文档。能看&#xff0c;但是不方便&#xff0c;需要逐个打开文档才能看到所需的内容&#xff0c;最好能将这些文档…

java 企业培训管理系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java 企业培训管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

Vulnhub靶机:basic_pentesting_2

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.4&#xff09; 靶机&#xff1a;basic_pentesting_2&#xff08;10.0.2.7&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://download.vulnhub.c…

未来已来:智慧餐饮点餐系统引领餐饮业的数字化转型

时下&#xff0c;智慧餐饮点餐系统正在引领着餐饮业迈向更高的位置。今天&#xff0c;小编将与大家共同探讨智慧餐饮点餐系统的发展趋势、优势以及对餐饮业的影响。 一、智慧餐饮点餐系统的发展趋势 智慧餐饮点餐系统的出现填补了这一空白&#xff0c;它通过引入数字化技术&a…

基于springboot+vue的医院资源管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

IDEA-DeBug理论与实践

文章目录 01_Debug简介和意义02_IDEA中的Debug步骤03_跳转到当前代码执行的行04_步过调试的使用05_步入调试的使用06_强制步入调试的使用07_步出调试的使用08_回退断点调试的使用09_运行到光标处10_计算表达式11_条件断点12_多线程调试 在软件开发中&#xff0c;IDEA&#xff0…

让燃油车再次破防 比亚迪汉唐荣耀双出击

相信很多读者朋友都还记得&#xff0c;就在不久前趣味科技曾经报道&#xff0c;2024龙年伊始比亚迪就打着“电比油低”的旗号&#xff0c;推出了价格仅为7.98万元起的秦PLUS、驱逐舰05荣耀版&#xff0c;向合资燃油车发起了大举进攻。 这两款“7”字头售价的车型上市&#xff0…

<网络安全>《60 概念讲解<第七课 网络模型OSI对应协议>》

1 OSI模型 OSI模型&#xff08;Open Systems Interconnection Model&#xff09;是一个由国际标准化组织&#xff08;ISO&#xff09;提出的概念模型&#xff0c;用于描述和标准化电信或计算系统的通信功能&#xff0c;以实现不同通信系统之间的互操作性。该模型将通信系统划分…

智能驾驶规划控制理论学习-基于采样的规划方法

目录 一、基于采样的规划方法概述 二、概率路图&#xff08;PRM&#xff09; 1、核心思想 2、实现流程 3、算法描述 4、节点连接处理 5、总结 三、快速搜索随机树&#xff08;RRT&#xff09; 1、核心思想 2、实现流程 3、总结 4、改进RRT算法 ①快速搜索随机图&a…

每日一类:QString类深入讲解

QString类是Qt框架中的一个核心组件&#xff0c;设计用于方便、高效地处理Unicode字符串。与标准C中的字符串处理方式相比&#xff0c;QString提供了更为丰富的API&#xff0c;支持国际化&#xff0c;并且内部使用UTF-16编码&#xff0c;能够处理世界上几乎所有的语言文字。 设…

Python图像形态学处理:腐蚀、膨胀、礼帽、黑帽……

文章目录 二值形态学灰度形态学 python图像处理教程&#xff1a;初步&#x1f4f7;插值变换 最基础的形态学操作有四个&#xff0c;分别是腐蚀、膨胀、开计算和闭计算&#xff0c;【scipy.ndimage】分别实现了二值数组和灰度数组的这四种运算。而针对灰度图像&#xff0c;【sc…

android路由表APP,携程Android面试题

大家应该看过很多分享面试成功的经验&#xff0c;但根据幸存者偏差的理论&#xff0c;也许多看看别人面试失败在哪里&#xff0c;对自己才更有帮助。 最近跟一个朋友聊天&#xff0c;他准备了几个月&#xff0c;刚刚参加完字节跳动面试&#xff0c;第二面结束后&#xff0c;嗯&…

(案例贴2) html+css 倒计时器

欢迎大家使用这个计时器噢 老哥直接附代码咯. timer.html <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">&l…

python 小游戏《2048》字符版非图形界面

参考链接&#xff1a; 闲谈2048小游戏和数组的旋转及翻转和转置 目录 2048 一、方阵类 二、随机插入1或2 三、 合并和递增 四、 判断和移动 五、 键盘控制 完整源代码 玩法过程 2048 上回说到2048小游戏中数组的各种旋转、翻转的方法&#xff0c;就是为代码编程作准…

非阻塞实现高效键盘扫描功能(STM32F4XX)

目录 概述 1 原理分析 1.1 技术背景 1.2 系统硬件 1.3 STM32 IO&#xff08;输入模式&#xff09;寄存器分析 1.3.1 输入IO的功能描述 1.3.2 输入配置 1.3.3 GPIO 寄存器&#xff08;输入模式相关&#xff09; 1.3.3.1 GPIO 端口模式寄存器 1.3.3.2 GPIO 端口上拉/下拉…

VUE3自定义文章排行榜的简单界面

文章目录 一、代码展示二、代码解读三、结果展示 一、代码展示 <template><div class"article-ranking"><div class"header"><h2 class"title">{{ title }}</h2></div><div class"ranking-list&qu…

单片机精进之路-9ds18b20温度传感器

ds18b20复位时序图&#xff0c;先将b20的数据引脚拉低至少480us&#xff0c;然后再将数据引脚拉高15-60us&#xff0c;再去将测传感器的数据引脚是不是变低电平并保持60-240us&#xff0c;如果是&#xff0c;则说明检测到温度传感器&#xff0c;并正常工作。需要在240us后才能检…

默频,主频,睿频

一、默频 默频就是跟在CPU型号后面的数字&#xff0c;如图中的CPU,默频是1.7GHZ 二、主频 可以理解为在运行中&#xff0c;电脑根据程序的需要进行调度&#xff0c;此时CPU的实际频率&#xff0c;在任务管理器中可以查看&#xff0c;如我的笔记本电脑在没有连接电源的情况下&…