【随笔】汇编(寄存器、内存模型、常用指令、语法)

news2024/11/15 17:57:22

文章目录

  • 一、简介
  • 二、寄存器
  • 三、内存模型
    • 3.1 Heap
    • 3.2 Stack
  • 四、指令
    • 4.1 示例
    • 4.2 语法
    • 4.3常用指令

一、简介

汇编语言(英语:assembly language)是任何一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。一种汇编语言专用于某种计算机系统结构,而不像许多高级语言,可以在不同系统平台之间移植。

使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程

汇编语言使用助记符Mnemonics)来代替和表示特定低级机器语言的操作。特定的汇编目标指令集可能会包括特定的操作数。许多汇编程序可以识别代表地址和常量的标签(Label)和符号(Symbols),这样就可以用字符来代表操作数而无需采取写死的方式。普遍地说,每一种特定的汇编语言和其特定的机器语言指令集是一一对应的。

许多汇编程序为程序开发、汇编控制、辅助调试提供了额外的支持机制。有的汇编语言编写工具经常会提供宏,它们也被称为宏汇编器。

现在汇编语言已不像其他大多数的程序设计语言一样被广泛用于程序设计,在今天的实际应用中,它通常被应用在底层硬件操作和高要求的程序优化的场合。驱动程序、嵌入式操作系统和实时运行程序中都会需要汇编语言

在这里插入图片描述

最早的时候,编写程序就是手写二进制指令,然后通过各种开关输入计算机,比如要做加法了,就按一下加法开关。后来,发明了纸带打孔机,通过在纸带上打孔,将二进制指令自动输入计算机。

为了解决二进制指令的可读性问题,工程师将那些指令写成了八进制。二进制转八进制是轻而易举的,但是八进制的可读性也不行。很自然地,最后还是用文字表达,加法指令写成 ADD。内存地址也不再直接引用,而是用标签表示。

这样的话,就多出一个步骤,要把这些文字指令翻译成二进制,这个步骤就称为 assembling,完成这个步骤的程序就叫做 assembler。它处理的文本,自然就叫做 aseembly code。标准化以后,称为 assembly language,缩写为 asm,中文译为汇编语言。

每一种 CPU 的机器指令都是不一样的,因此对应的汇编语言也不一样。本文介绍的是目前最常见的 x86 汇编语言。

  1. x86/x64 汇编:这是应用最广泛的汇编语言类型,用于 Intel 和 AMD 等 x86 架构的处理器,包括 32 位 (x86) 和 64 位 (x64)。这种汇编语言在 PC、服务器和许多嵌入式系统中被广泛使用。
  2. ARM 汇编:ARM 架构是另一种常见的处理器架构,广泛应用于移动设备、嵌入式系统以及一些服务器。ARM 汇编用于编写针对 ARM 处理器的低级代码。
  3. MIPS 汇编:MIPS 是一种 RISC 架构(精简指令集计算机),在一些嵌入式系统和早期的个人计算机中使用。MIPS 汇编用于编写针对 MIPS 处理器的低级代码。
  4. 68k 汇编:68k 汇编用于 Motorola 68k 系列处理器,这些处理器在历史上曾经被广泛应用于个人电脑、工作站和嵌入式系统中。
  5. AVR 汇编:AVR 是一种常见的低功耗、高性能的 8 位和 32 位微控制器架构,用于许多嵌入式系统和物联网设备。AVR 汇编用于编写针对 AVR 微控制器的低级代码。
  6. PowerPC 汇编:PowerPC 是 IBM、Motorola 和苹果公司合作开发的一种处理器架构,曾被广泛应用于苹果电脑和一些服务器中。PowerPC 汇编用于编写针对 PowerPC
    处理器的低级代码。

二、寄存器

寄存器(Register)是CPU 内用来暂存指令、数据和地址的电脑存储器。寄存器的存贮容量有限,读写速度非常快。在计算机体系结构里,寄存器存储在已知时间点所作计算的中间结果,通过快速地访问数据来加速计算机程序的执行。

寄存器位于存储器层次结构的最顶端,也是CPU可以读写的最快的存储器,事实上所谓的暂存已经不像存储器,而是非常短暂的读写少量信息并马上用到,因为通常程序执行的步骤中,这期间就会一直使用它。寄存器通常都是以他们可以保存的比特数量来计量,举例来说,一个8位寄存器或32位寄存器。在中央处理器中,包含寄存器的部件有指令寄存器(IR)、程序计数器和累加器。寄存器现在都以寄存器数组的方式来实现,但是他们也可能使用单独的触发器、高速的核心存储器、薄膜存储器以及在数种机器上的其他方式来实现出来。
在这里插入图片描述

寄存器也可以指代由一个指令之输出或输入可以直接索引到的寄存器组群,这些寄存器的更确切的名称为“架构寄存器”。例如,x86指令集定义八个32位寄存器的集合,但一个实现x86指令集的CPU内部可能会有八个以上的寄存器。

32位 CPU、64位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4个字节。

x86架构CPU中常见的寄存器,包括通用寄存器、段寄存器、标志寄存器和指令指针寄存器。这些寄存器在不同的x86处理器中可能会有所不同,但是以下是通用的:

寄存器描述
EAX累加器 (Accumulator)
EBX基址寄存器 (Base)
ECX计数寄存器 (Counter)
EDX数据寄存器 (Data)
ESI源索引寄存器 (Source Index)
EDI目的索引寄存器 (Destination Index)
ESP栈指针寄存器 (Stack Pointer)
EBP基址指针寄存器 (Base Pointer)
EIP指令指针寄存器 (Instruction Pointer)
CS代码段寄存器 (Code Segment)
DS数据段寄存器 (Data Segment)
SS栈段寄存器 (Stack Segment)
ES附加段寄存器 (Extra Segment)
FS附加段寄存器 (Extra Segment)
GS附加段寄存器 (Extra Segment)
EFLAGS标志寄存器 (Flags)

这些寄存器的前缀"E"表示32位寄存器,若是64位寄存器,则为"R"。例如,64位下的累加器寄存器为RAX。(16位则没有前缀)

三、内存模型

3.1 Heap

寄存器只能存放很少量的数据,大多数时候,CPU 要指挥寄存器,直接跟内存交换数据。所以,除了寄存器,还必须了解内存怎么储存数据。

程序运行的时候,操作系统会给它分配一段内存,用来储存程序和运行产生的数据。这段内存有起始地址和结束地址,比如从0x1000到0x8000,起始地址是较小的那个地址,结束地址是较大的那个地址。

程序运行过程中,对于动态的内存占用请求(比如新建对象,或者使用malloc等命令),系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。举例来说,用户要求得到10个字节内存,那么从起始地址0x1000开始给他分配,一直分配到地址0x100A,如果再要求得到22个字节,那么就分配到0x1020
在这里插入图片描述

这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。

“堆”(Heap)也可以指代一种数据结构,与内存中的堆不同。在数据结构中,堆通常指的是一种特殊的树形结构,用于实现优先队列。

3.2 Stack

除了 Heap 以外,其他的内存占用叫做 Stack(栈)。简单说,Stack 是由于函数运行而临时占用的内存区域。

int main() {
   int a = 2;
   int b = 3;
}

上面代码中,系统开始执行main函数时,会为它在内存里面建立一个frame),所有main的内部变量(比如a和b)都保存在这个帧里面。main函数执行结束后,该帧就会被回收,释放所有的内部变量,不再占用空间。

int main() {
   int a = 2;
   int b = 3;
   return add_ab(a, b);
}

上面代码中,main函数内部调用了add_ab函数。执行到这一行的时候,系统也会为add_ab新建一个帧,用来储存它的内部变量。也就是说,此时同时存在两个帧:main和add_ab。一般来说,调用栈有多少层,就有多少帧。

等到add_ab运行结束,它的帧就会被回收,系统会回到函数main刚才中断执行的地方,继续往下执行。通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。

所有的帧都存放在 Stack,由于帧是一层层叠加的,所以 Stack 叫做栈。生成新的帧,叫做"入栈"(push);栈的回收叫做"出栈(pop)。Stack 的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束运行),这就叫做"后进先出(LIFO)"的数据结构。每一次函数执行结束,就自动释放一个帧,所有函数执行结束,整个 Stack 就都释放了。
在这里插入图片描述

Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。 比如,内存区域的结束地址是0x8000,第一帧假定是16字节,那么下一次分配的地址就会从0x7FF0开始;第二帧假定需要64字节,那么地址就会移动到0x7FB0。

四、指令

4.1 示例

C语言:

在这里插入图片描述
转成汇编:

gcc -S temp.c

汇编代码:一个高级语言的简单操作,底层可能由几个,甚至几十个 CPU 指令构成。CPU 依次执行这些指令,完成这一步操作。(注释是我加的)

        .file   "temp.c"             ; 源文件名称
        .text                        ; 开始文本段
        .globl  add_ab               ; 声明 add_ab 是一个全局函数
        .type   add_ab, @function    ; 指定 add_ab 是一个函数类型
add_ab:                             ; 函数 add_ab 的开始
.LFB0:
        .cfi_startproc
        endbr64                     ; 提高对分支目标地址(BTA)的保护
        pushq   %rbp                ; 保存旧的栈基址指针
        .cfi_def_cfa_offset 16      ; 指定栈上的数据偏移量
        .cfi_offset 6, -16          ; 指定寄存器 %rbp 的偏移
        movq    %rsp, %rbp          ; 设置 %rbp 指向当前栈顶,建立新的栈帧
        .cfi_def_cfa_register 6     ; 指定 %rbp 为当前帧指针
        movl    %edi, -4(%rbp)      ; 将函数参数 %edi(第一个参数)存储到栈上的位置
        movl    %esi, -8(%rbp)      ; 将函数参数 %esi(第二个参数)存储到栈上的位置
        movl    -4(%rbp), %edx      ; 从栈中加载第一个参数 %edx
        movl    -8(%rbp), %eax      ; 从栈中加载第二个参数 %eax
        addl    %edx, %eax          ; 将 %edx 和 %eax 相加
        popq    %rbp                ; 恢复之前的栈基址指针
        .cfi_def_cfa 7, 8           ; 指定栈顶为 %rsp,栈基址为 %rbp
        ret                         ; 函数返回
        .cfi_endproc
.LFE0:
        .size   add_ab, .-add_ab    ; 指定函数 add_ab 的大小
        .section        .rodata      ; 开始只读数据段
.LC0:
        .string "x+y=%d"             ; 定义一个字符串常量,用于 printf 函数
        .text                        ; 返回文本段
        .globl  main                 ; 声明 main 函数为全局函数
        .type   main, @function      ; 指定 main 是一个函数类型
main:                               ; main 函数的开始
.LFB1:
        .cfi_startproc
        endbr64                     ; 提高对分支目标地址(BTA)的保护
        pushq   %rbp                ; 保存旧的栈基址指针
        .cfi_def_cfa_offset 16      ; 指定栈上的数据偏移量
        .cfi_offset 6, -16          ; 指定寄存器 %rbp 的偏移
        movq    %rsp, %rbp          ; 设置 %rbp 指向当前栈顶,建立新的栈帧
        .cfi_def_cfa_register 6     ; 指定 %rbp 为当前帧指针
        subq    $16, %rsp           ; 为局部变量分配空间
        movl    $2, -8(%rbp)        ; 将常量 2 存储到栈上的位置(第一个局部变量)
        movl    $3, -4(%rbp)        ; 将常量 3 存储到栈上的位置(第二个局部变量)
        movl    -4(%rbp), %edx      ; 从栈中加载第一个局部变量 %edx
        movl    -8(%rbp), %eax      ; 从栈中加载第二个局部变量 %eax
        movl    %edx, %esi          ; 复制 %edx 到 %esi
        movl    %eax, %edi          ; 复制 %eax 到 %edi
        call    add_ab              ; 调用 add_ab 函数
        movl    %eax, %esi          ; 将返回值(和)存储到 %esi 中
        leaq    .LC0(%rip), %rdi    ; 将字符串地址存储到 %rdi 中
        movl    $0, %eax            ;0 存储到 %eax
        call    printf@PLT          ; 调用 printf 函数
        movl    $0, %eax            ;0 存储到 %eax,表示返回值为 0
        leave                       ; 恢复栈帧
        .cfi_def_cfa 7, 8           ; 指定栈顶为 %rsp,栈基址为 %rbp
        ret                         ; 函数返回
        .cfi_endproc
.LFE1:
        .size   main, .-main        ; 指定函数 main 的大小
        .ident  "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0" ; 编译器的版本信息
        .section        .note.GNU-stack,"",@progbits    ; 用于指示栈是否是可执行的
        .section        .note.gnu.property,"a"           ; GNU 属性节
        .align 8
        .long    1f - 0f
        .long    4f - 1f
        .long    5
0:
        .string  "GNU"
1:
        .align 8
        .long    0xc0000002
        .long    3f - 2f
2:
        .long    0x3
3:
        .align 8
4:

4.2 语法

汇编语言的语法可以根据不同的体系结构有所不同,以下是 x86 架构汇编语言的一般语法:

指令格式:

[label:]   mnemonic   [operands]   [;comment]
  • label:可选的,用于标识代码的位置,以冒号结尾。
  • mnemonic:指令助记符,代表要执行的操作。
  • operands:操作数,指定指令要操作的数据。
  • comment:注释,用于解释指令的作用或其他信息。

基本元素:

  • 指令(Instructions):表示要执行的操作,如 mov, add, sub, jmp 等。
  • 寄存器(Registers):用于存储数据和执行操作,如 eax, ebx, ecx 等。
  • 内存操作数(Memory Operands):表示内存中的数据,如 [address][ebx] 等。
  • 立即数(Immediate Values):常数值,直接指定在指令中,如 5, 0xFF, 0b1010 等。
  • 标签(Labels):用于标识代码的位置,通常在循环或条件跳转中使用。

指令示例:

; 注释示例
section .text             ; 代码段开始

    global _start         ; 全局声明,程序入口

_start:
    mov eax, 5            ; 将立即数 5 存入 eax 寄存器
    mov ebx, 3            ; 将立即数 3 存入 ebx 寄存器
    add eax, ebx          ; eax = eax + ebx
    sub eax, 1            ; eax = eax - 1

    cmp eax, 10           ; 比较 eax 和立即数 10
    jl  less_than_ten     ; 如果 eax < 10,则跳转到 less_than_ten 标签

    mov eax, 10           ; 如果不满足条件,将立即数 10 存入 eax

less_than_ten:
    mov ecx, eax          ; 将 eax 的值存入 ecx

    ; 调用系统调用 exit
    mov eax, 1            ; 系统调用号 1 表示 exit
    xor ebx, ebx          ; 退出码为 0
    int 0x80              ; 调用系统调用

section .data             ; 数据段开始
    my_var db 'A'         ; 定义一个名为 my_var 的字节数据,值为字符 'A'

汇编语言对空白字符不敏感,但大小写敏感。同时,注释以分号 ; 开始,可以在语句后添加注释。

4.3常用指令

常用的汇编指令以及简要说明和示例:

指令说明示例
mov将数据从一个位置复制到另一个位置mov eax, ebx
add加法操作add eax, ebx
sub减法操作sub eax, ebx
mul无符号乘法mul ebx
div无符号除法div ebx
and按位与操作and eax, ebx
or按位或操作or eax, ebx
xor按位异或操作xor eax, ebx
not按位取反操作not eax
shl逻辑左移shl eax, 1
shr逻辑右移shr eax, 1
cmp比较两个操作数的值,设置标志位cmp eax, ebx
test测试两个操作数的位并设置标志位test eax, ebx
jmp无条件跳转jmp label
je等于时跳转je label
jne不等于时跳转jne label
jl小于时跳转jl label
jg大于时跳转jg label
jle小于等于时跳转jle label
jge大于等于时跳转jge label
call调用函数或过程call function_name
ret从函数返回ret
push将数据压入栈push eax
pop从栈中弹出数据pop eax
nop空操作nop
int产生软中断int 0x80
hlt暂停处理器执行hlt

示例:

假设有以下寄存器的初始值:

  • eax = 5
  • ebx = 3

示例汇编指令和注释如下:

; 将 ebx 加到 eax 中
add eax, ebx    ; eax = 5 + 3 = 8

; 乘以 ebx 的值
mul ebx         ; eax = 8 * 3 = 24

; 将结果保存到变量 result 中
mov [result], eax ; result = 24

; 比较 eax 和 ebx 的值
cmp eax, ebx    ; 设置标志位,用于后续条件跳转

; 如果 eax 大于 ebx,跳转到 label1
jg label1       ; 如果 24 > 3,则跳转

; 否则跳转到 label2
jmp label2

label1:
; 如果跳转到这里,说明 24 > 3
; 这里可以添加一些操作

label2:
; 如果跳转到这里,说明 24 <= 3
; 这里可以添加一些操作

; 函数调用示例
call my_function  ; 调用函数 my_function

; 从函数返回
ret

; 定义一个标签,用于跳转
label3:
; 这里可以添加一些操作

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

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

相关文章

HUAWEI Pocket 2外屏实时查看App动态,小小窗口大便捷

当我们点外卖、等候飞机时&#xff0c;不少人习惯频繁点亮手机查看外卖配送进度、值机时间。 这时候&#xff0c;手机亮屏、解锁、打开对应App查看状态对于我们来说就显得非常繁琐。而华为Pocket 2结合HarmonyOS 4系统的实况窗功能&#xff0c;与常显外屏的搭配使用&#xff0…

PTA黑暗料理

小明最近正开发一个料理手游。游戏中仓库里有一些原材料&#xff0c;每个原材料都不相同。在游戏后台&#xff0c;每个原材料由两个不同整数构成。当烹饪锅有至少N&#xff08;N≥2&#xff09;个原材料且其中有N个原材料正好包含N个不同的整数&#xff08;即这N个整数每个出现…

【Java 数据结构】Map和Set的介绍

目录 1、Map 和 Set 的概念 2、模型 3、Map 的学习 3.1 关于 Map.Entry 3.2 Map 的常用方法 4、Set 的常用方法 5、 Map 和 Set 的注意点 1、Map 和 Set 的概念 Java 提供了 Map 和 Set 的接口&#xff0c;是一种专门用来进行搜索的容器或数据结构&#xff0c;而他搜索…

【Maven篇】解锁 Maven 的智慧:依赖冲突纷争下的版本调停者

缘起 软件开发世界是一个充满无限可能的领域&#xff0c;但同时也伴随着诸多挑战。其中之一&#xff0c;就是依赖冲突的问题。在这篇文章中&#xff0c;我们将揭开 Maven 这位“版本调停者”的神秘面纱&#xff0c;深入探讨如何在版本纠纷的盛宴中解决依赖问题。 Maven&#…

Ansible的介绍、安装与部署

Ansible的介绍、安装与部署 文章目录 Ansible的介绍、安装与部署1. 介绍 Ansible1.1 什么是 Ansible?1.2 Ansible 无需代理1.3 Ansible 方式 2. 安装需求2.1 控制节点2.2 受管主机2.3 基于Windows的受管主机2.4 受管网络设备 3. 管理Ansible配置文件3.1 配置Ansible3.2 配置文…

JAVA后端调用OpenAI接口 实现打字机效果(SSE)

SSE SSE&#xff08;Server-Sent Events&#xff0c;服务器发送事件&#xff09;是一种基于HTTP协议的通信技术&#xff0c;它允许服务器持续地将数据推送给客户端&#xff0c;而无需客户端发起请求。这种通信方式通常用于实时性要求较高的场景&#xff0c;如实时更新、通知、或…

AJAX-原理XMLHttpRequest

定义 使用 查询参数 定义&#xff1a;浏览器提供给服务器的额外信息&#xff0c;让服务器返回浏览器想要的数据 语法&#xff1a;http://xxxx.com/xxx/xxx?参数名1值1&参数名2值2

敏捷开发——elementUI/Vue使用/服务器部署

1. 创建vue项目 2. 安装element-ui组件库 npm i -S element-ui或 npm install element-ui3. 在main.js中导入element-ui组件 import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css Vue.use(ElementUI)4. 运行 npm run serve后可以使用 ctrc终止进…

C/C++动态链接库的封装和调用

1 引言 静态链接库是在编译时被链接到程序中的库文件&#xff0c;在编译时&#xff0c;链接器将静态链接库的代码和数据复制到最终的可执行文件中。动态链接库是在程序运行时加载的库文件&#xff0c;在编译时&#xff0c;可执行文件只包含对动态链接库的引用&#xff0c;而不…

软件杯 深度学习 python opencv 火焰检测识别 火灾检测

文章目录 0 前言1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数据集准备5.1 数…

leetcode 3081

leetcode 3081 题目 例子 思路 使用minheap 记录字符出现频次 代码 class Solution { public:string minimizeStringValue(string s) {int freq[26]{};for(char c: s){if(c ! ?){freq[c-a];}}//std::greater<> 比较器比较 pair 对象时&#xff0c;默认比较规则是先比…

leetcode刷题(javaScript)——动态规划相关场景题总结

动态规划在 JavaScript 刷题中有一定的难度&#xff0c;但也是非常常见和重要的算法思想。动态规划通常适用于需要求解最优解、最大值、最小值等问题的场景&#xff0c;可以将复杂问题拆分成子问题&#xff0c;通过存储子问题的解来避免重复计算&#xff0c;从而提高效率。 理解…

elk收集k8s微服务日志

一、前言 使用filebeat自动发现收集k8s的pod日志&#xff0c;这里分别收集前端的nginx日志&#xff0c;还有后端的服务java日志&#xff0c;所有格式都是用json格式&#xff0c;建议还是需要让开发人员去输出java的日志为json&#xff0c;logstash分割java日志为json格式&#…

Transformer的前世今生 day01(预训练、统计语言模型)

预训练 在相似任务中&#xff0c;由于神经网络模型的浅层是通用的&#xff0c;如下图&#xff1a; 所以当我们的数据集不够大&#xff0c;不能产生性能良好的模型时&#xff0c;可以尝试让模型B在用模型A的浅层基础上&#xff0c;深层的部分自己生成参数&#xff0c;减小数据集…

京津冀自动驾驶产业盛会“2024北京国际自动驾驶技术展览会”

随着科技的飞速发展&#xff0c;自动驾驶技术成为了汽车产业变革的热点和前沿。智能化、网联化已经成为推动汽车产业创新发展的重要力量&#xff0c;而自动驾驶技术则是其中的关键一环。它不仅能够提高道路安全性、缓解交通拥堵&#xff0c;还能为乘客带来更加舒适、便捷的出行…

注册个人小程序

访问地址 https://mp.weixin.qq.com/ 立即注册 选择小程序 注册 填写信息 登录邮箱 访问邮箱的链接激活账号 选择个人&#xff0c;填写信息 注册完成&#xff0c;即可登录进入填写信息

苍穹外卖-day15:套餐管理

套餐管理 课程内容 套餐分页查询启售停售套餐删除套餐新增套餐 1. 套餐分页查询 1.1 需求分析和接口设计 根据产品原型来了解需求&#xff0c;套餐分页查询的产品原型如下&#xff1a; 业务规则&#xff1a; 根据页码展示套餐信息(套餐名称、套餐图片、套餐分类、价格、售…

qt+ffmpeg 实现音视频播放(二)之音频播放

一、音频播放流程 1、打开音频文件 通过 avformat_open_input() 打开媒体文件并分配和初始化 AVFormatContext 结构体。 函数原型如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options); 参数说…

数据分析-Pandas的Andrews曲线可视化解读

数据分析-Pandas的Andrews曲线可视化解读 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据…

C#,图论与图算法,无向图(Graph)回环(Cycle)的不相交集(disjoint)或并集查找(union find)判别算法与源代码

1 回环(Cycle)的不相交集(disjoint)或并集 不相交集数据结构是一种数据结构,它跟踪划分为多个不相交(非重叠)子集的一组元素。联合查找算法是对此类数据结构执行两个有用操作的算法: 查找:确定特定元素所在的子集。这可用于确定两个元素是否在同一子集中。 并集:将…