第9章 子程序与函数调用
汇编语言是一种低级编程语言,它几乎是一对一地映射到计算机的机器码指令。在汇编语言中实现循环结构通常涉及到使用条件跳转指令(如 JMP
、JE
、JNE
等)来控制程序流程。下面我将通过一个简单的例子来讲解如何用x86汇编语言实现一个循环结构。
假设我们要编写一个程序,它会计算从1加到N(包括N)的和,并将结果存储在一个变量中。我们将使用NASM语法(Netwide Assembler),这是一个常用的x86汇编语言汇编器。
section .dataN db 5 ; 我们要加到的数字N, 这里设置为5sum db 0 ; 存储结果的变量section .bssi resb 1 ; 用于循环计数的变量,预留1字节空间section .textglobal _start_start:mov byte [i], 1 ; 初始化循环变量i = 1mov al, [i] ; 将i的值加载到AL寄存器mov bl, [sum] ; 将sum的值加载到BL寄存器loop_start:cmp al, [N] ; 比较i与Njg loop_end ; 如果i > N,则结束循环 (Jump if Greater)add bl, al ; 否则,将i加到sum上inc al ; i++mov [sum], bl ; 更新sum的值jmp loop_start ; 跳回到循环开始处loop_end:; 循环结束后可以在这里添加其他代码; 退出程序(Linux系统调用)mov eax, 60 ; syscall: exitxor edi, edi ; status: 0syscall ; 执行系统调用
上述代码中:
section .data
定义了数据段,其中包含了程序运行时需要的数据。section .bss
定义了未初始化的数据段,这里我们定义了一个未初始化的变量i
用来作为循环计数器。section .text
定义了代码段,这是程序执行的指令所在的地方。_start
是程序的入口点。mov
指令用于移动数据。cmp
指令用于比较两个操作数。jg
指令是条件跳转,如果前面的比较结果是第一个操作数大于第二个操作数,则跳转。add
和inc
分别是加法和自增指令。jmp
是无条件跳转指令,它会让程序跳转到指定的标签处继续执行。- 在循环结束后,我们使用了系统调用
exit
来终止程序。
这个程序是一个非常基础的例子,展示了如何在汇编语言中实现一个简单的循环。实际的汇编程序可能会更复杂,涉及到更多的寄存器使用、内存管理和更复杂的控制流。
当然,我们可以再深入一点,来看一个稍微复杂一点的例子。这次我们将实现一个程序,它会计算一个数组中所有元素的和,并将结果存储在一个变量中。我们还会展示如何使用寄存器来保存数组索引和累加的结果。
假设我们要计算一个包含8个字节大小(每个元素1字节)的整数数组中所有元素的和。我们将再次使用NASM语法。
section .dataarray db 1, 2, 3, 4, 5, 6, 7, 8 ; 数组初始化,8个元素len equ $ - array ; 计算数组长度(以字节为单位)sum dd 0 ; 定义一个双字(4字节)用于存储总和section .textglobal _start_start:mov ecx, len ; 将数组长度(字节数)加载到ECX寄存器xor eax, eax ; 清空EAX寄存器,用于累加求和lea ebx, [array] ; 加载数组首地址到EBX寄存器sum_loop:cmp ecx, 0 ; 比较剩余元素数量是否为0je sum_end ; 如果是,则跳转到sum_end结束循环add al, [ebx] ; 将当前元素添加到AL(EAX的低8位)inc ebx ; 移动到下一个元素dec ecx ; 剩余元素计数减1jmp sum_loop ; 跳回循环开始处继续执行sum_end:mov [sum], eax ; 将最终的和存储到sum变量中; 打印结果或进行其他处理...; 退出程序(Linux系统调用)mov eax, 60 ; syscall: exitxor edi, edi ; status: 0syscall ; 执行系统调用
在这个例子中:
len
是一个宏定义,它通过计算当前位置与数组起始位置之间的距离来确定数组的长度。sum
使用了dd
来定义一个双字(32位),用来存储最终的累加和。mov ecx, len
把数组的长度加载到ECX
寄存器中,这个寄存器通常用来作为循环计数器。xor eax, eax
用来清空EAX
寄存器,这等价于EAX = 0
,因为XOR
操作数相同时结果为0。lea ebx, [array]
使用了LEA
指令来加载数组的地址,而不是值,所以EBX
将指向数组的第一个元素。add al, [ebx]
把当前元素添加到AL
中,这是EAX
寄存器的最低8位。请注意,这里假定数组中的数据是单字节的;如果需要处理更大的数值,应该调整指令以适应相应的寄存器部分。inc ebx
和dec ecx
分别递增指针和递减计数器。jmp sum_loop
和之前的条件跳转一起构成了循环结构。
这段代码是一个简单的示例,展示了如何在汇编语言中遍历一个数组并计算其元素的和。实际应用中可能还需要考虑更多的细节,比如处理不同大小的数据类型、优化性能以及确保代码的安全性和稳定性。
让我们进一步扩展案例。这次我们将实现一个汇编程序,它不仅会计算数组中所有元素的和,还会找出数组中的最大值,并将这两个结果打印出来。为了完成这个任务,我们需要使用系统调用来输出结果。这里我们假设运行环境是Linux,并且我们要处理的是32位整数数组。
section .dataarray dd 10, 20, 30, 40, 50, 60, 70, 80 ; 数组初始化,8个元素(每个元素4字节)len equ ($ - array) / 4 ; 计算数组长度(以元素为单位)sum dd 0 ; 定义一个双字用于存储总和max dd 0 ; 定义一个双字用于存储最大值format db "Sum: %d, Max: %d", 10, 0 ; 打印格式字符串section .bssbuffer resb 16 ; 用于格式化输出的缓冲区section .textextern printf ; 外部C库函数printfglobal _start_start:mov ecx, len ; 将数组长度加载到ECX寄存器xor eax, eax ; 清空EAX寄存器,用于累加求和mov ebx, [array] ; 初始化最大值为数组的第一个元素lea esi, [array] ; 加载数组首地址到ESI寄存器sum_and_max_loop:cmp ecx, 0 ; 比较剩余元素数量是否为0je print_results ; 如果是,则跳转到print_results结束循环add eax, [esi] ; 将当前元素添加到EAX中进行求和cmp ebx, [esi] ; 比较当前元素与EBX中的最大值cmovg ebx, [esi] ; 如果当前元素更大,则更新最大值add esi, 4 ; 移动到下一个元素(每个元素4字节)dec ecx ; 剩余元素计数减1jmp sum_and_max_loop ; 跳回循环开始处继续执行print_results:mov [sum], eax ; 将最终的和存储到sum变量中mov [max], ebx ; 将找到的最大值存储到max变量中; 准备参数并调用printf打印结果push dword [max] ; 第二个参数:最大值push dword [sum] ; 第一个参数:总和push dword format ; 格式字符串call printf ; 调用printfadd esp, 12 ; 清理堆栈上的三个参数 (4 * 3); 退出程序(Linux系统调用)mov eax, 60 ; syscall: exitxor edi, edi ; status: 0syscall ; 执行系统调用
在这个例子中:
array
是一个包含8个32位整数的数组。len
通过宏定义计算出数组的长度,这里除以4是因为每个元素是4字节大小。sum
和max
分别用于存储求和的结果和找到的最大值。format
是一个格式化字符串,用于告诉printf
如何格式化输出。buffer
在.bss
段中预留了空间,虽然在这个例子中没有直接使用,但在实际应用中可能会用到,比如当需要构建复杂的输出字符串时。extern printf
表明我们将会使用外部的C库函数printf
来输出结果。cmovg
是条件移动指令,只有当比较操作的结果为真(即当前元素大于EBX)时才会执行数据移动。- 在
print_results
部分,我们准备了参数并调用了printf
来打印求和的结果和最大值。 - 最后,我们清理了堆栈上的参数(因为调用惯例要求调用者清理),然后通过系统调用
exit
终止程序。
请注意,在真实环境中编写和链接这样的程序时,您需要确保您的开发环境支持调用C库函数如 printf
,并且正确设置了链接器选项来链接标准C库。此外,这段代码假定你是在32位Linux系统上运行;对于64位系统,系统调用编号和参数传递方式会有所不同。
我们可以进一步探讨汇编语言中子程序与函数调用的更多细节,比如参数传递、返回值处理、局部变量的使用、栈帧的建立和销毁,以及不同的调用约定。
参数传递
在上述例子中,我们通过堆栈来传递参数。这是一种常见的方法,尤其是在较老的或某些特定的调用约定中。然而,在现代系统和一些优化的调用约定(如System V AMD64 ABI)中,前几个参数通常通过寄存器传递以提高效率。例如,在x86-64 Linux中,整数和指针参数按照以下顺序通过寄存器传递:%rdi
, %rsi
, %rdx
, %rcx
, %r8
, %r9
。
返回值
返回值通常存储在一个特定的寄存器中。对于整数和指针类型,通常是 EAX
寄存器(32位模式)或 RAX
寄存器(64位模式)。如果返回值较大,可能需要通过额外的机制来处理,例如通过隐式或显式的输出参数。
局部变量
局部变量通常位于栈上。当进入一个子程序时,可以通过调整堆栈指针 %esp
或者在64位模式下 %rsp
来为局部变量分配空间。这通常是在保存旧基址指针并设置新基址指针之后完成的。局部变量的空间可以从当前的堆栈指针减去相应的字节数来获得。
subl $16, %esp # 分配16个字节用于局部变量(32位)
# 或者在64位模式下
subq $16, %rsp # 分配16个字节用于局部变量(64位)
栈帧
栈帧是调用子程序时创建的数据结构,它包含了子程序的局部变量、保存的寄存器值、返回地址等。在上面的例子中,我们通过保存和恢复 %ebp
来管理栈帧。在更复杂的子程序中,你可能会有多个层级的嵌套调用,因此正确地管理栈帧非常重要。
调用约定
调用约定定义了如何进行函数调用的一组规则,包括参数传递方式、哪个寄存器需要被调用者保存、哪个可以被被调用者覆盖、返回值如何传递等等。不同的平台和编译器有不同的调用约定。了解你的目标平台上的调用约定是非常重要的,因为它们决定了你的汇编代码是否能与其他代码正确交互。
示例:带有局部变量和返回值的子程序
这里有一个稍微复杂一点的例子,展示了一个带有局部变量的子程序,并且它还返回一个值:
.section .data# 数据段.section .text.globl _start_start:# 准备参数movl $5, %edi # 第一个加数 (在64位下使用 %edi)movl $3, %esi # 第二个加数 (在64位下使用 %esi)call add_numbers # 调用add_numbers子程序# 子程序返回后,结果存储在EAX/RAX寄存器中# 现在我们可以使用这个结果进行其他操作...movl $60, %eax # syscall: sys_exit (64位下使用 %rax 和 syscall 指令)xor %edi, %edi # 设置退出状态为0syscall # 调用内核中断.type add_numbers, @function
add_numbers:push %rbp # 保存旧的基址指针mov %rsp, %rbp # 设置新的基址指针sub $16, %rsp # 为局部变量分配空间# 此时,栈帧如下:# [rbp+16] - 返回地址# [rbp+8] - 第一个参数# [rbp+0] - 局部变量空间mov %edi, -4(%rbp) # 将第一个参数保存到局部变量mov %esi, -8(%rbp) # 将第二个参数保存到局部变量mov -4(%rbp), %eax # 获取第一个局部变量到EAXadd -8(%rbp), %eax # 将第二个局部变量加到EAXleave # 恢复旧的基址指针并释放局部变量空间ret # 返回到调用者
在这个例子中,我们演示了如何在子程序中使用局部变量,并且展示了 leave
指令,它是 mov %rbp, %rsp
和 pop %rbp
的组合指令,用来清理栈帧。此外,我们还展示了64位Linux下的系统调用规范,其中使用了 syscall
指令来执行系统调用。
既然您希望继续深入探讨,我们可以进一步讨论一些高级话题,比如:
- 调用约定和寄存器保存规则
- 子程序的递归调用
- 使用栈帧指针(Frame Pointer)优化调试和异常处理
- 子程序中的变长参数列表
- 内联汇编与混合编程
调用约定和寄存器保存规则
在不同的平台和环境中,调用约定会有所不同。以x86-64 Linux为例,根据System V AMD64 ABI,以下是一些重要的规则:
- 整数或指针参数通过寄存器
%rdi
,%rsi
,%rdx
,%rcx
,%r8
,%r9
传递。 - 浮点数参数通过 XMM 寄存器传递。
- 返回值通常放在
%rax
或%xmm0
中(对于浮点返回值)。 - 某些寄存器是“被调用者保存”的(callee-saved),如
%rbx
,%rbp
,%r12
到%r15
;而其他寄存器则是“调用者保存”的(caller-saved),如%rax
,%rcx
,%rdx
,%rsi
,%rdi
等等。
这意味着如果一个子程序想要使用被调用者保存的寄存器,它必须先保存这些寄存器的原始值,并在退出前恢复它们。
子程序的递归调用
递归调用是指函数直接或间接地调用自身。在汇编中实现递归需要特别注意栈的管理,因为每次递归调用都会创建一个新的栈帧。例如,计算阶乘的递归子程序可能会如下所示:
.type factorial, @function
factorial:push %rbp # 保存旧的基址指针mov %rsp, %rbp # 设置新的基址指针mov %edi, -4(%rbp) # 保存参数 n 到局部变量cmp $1, -4(%rbp) # 如果 n <= 1jle .Lbase_case # 跳转到基础情况dec -4(%rbp) # n = n - 1call factorial # 递归调用 factorial(n-1)imul -4(%rbp), %eax # 将结果乘以 n.Lbase_case:mov $1, %eax # 基础情况:返回 1leave # 恢复旧的基址指针并释放局部变量空间ret # 返回到调用者
请注意,这个例子简化了错误检查和边界条件处理。
使用栈帧指针优化调试和异常处理
栈帧指针(通常是 %ebp
在32位模式下,%rbp
在64位模式下)用于建立链表结构,这有助于调试器追踪函数调用历史和回溯堆栈。此外,在发生异常时,操作系统或运行时环境可以利用栈帧来找到正确的异常处理程序。
子程序中的变长参数列表
C语言中的 printf
函数就是一个接受变长参数列表的例子。为了在汇编中实现类似的功能,我们需要遵循特定的协议,如在 x86-64 上使用 AL 寄存器来指定变长参数的数量,并且这些参数仍然按照调用约定通过寄存器或堆栈传递。
内联汇编与混合编程
有时你可能想在高级语言(如C/C++)中嵌入汇编代码,这被称为内联汇编。GCC 和 Clang 编译器支持这种特性,允许你在 C/C++ 函数内部插入汇编指令。这对于性能关键部分或者硬件交互非常有用。然而,编写内联汇编需要仔细考虑寄存器分配、内存访问和调用约定等问题。
以上内容涵盖了更多关于汇编语言中子程序与函数调用的细节。希望这些信息对您有所帮助。
相关文章:

第9章 子程序与函数调用
汇编语言是一种低级编程语言,它几乎是一对一地映射到计算机的机器码指令。在汇编语言中实现循环结构通常涉及到使用条件跳转指令(如 JMP、JE、JNE 等)来控制程序流程。下面我将通过一个简单的例子来讲解如何用x86汇编语言实现一个循环结构。 …...

manacher算法
Manacher 算法快速入门 Manacher 算法是一种用于寻找字符串中最长回文子串的高效算法,时间复杂度为 O(n)。 基本概念 回文 回文是一个字符串,从左到右和从右到左读都一样。 示例: 回文:"aba"、"abba"非回…...

Cocos2dx Lua绑定生成中间文件时参数类型与源码类型不匹配
这两天维护的一个项目,使用arm64-v8a指令集编译时遇到了报错,提示类型不匹配,具体报错的代码【脚本根据C源文件生成的中间文件】如下: const google::protobuf::RepeatedField<unsigned long long>& ret cobj->equi…...

为什么需要 std::call_once?
std::call_once 是 C 标准库中的一个函数,用来确保某个操作仅被执行一次,通常用于线程安全的初始化操作。它常与 std::once_flag 结合使用,后者用于标记某个操作是否已经执行过。 为什么需要 std::call_once? 在多线程程序中&am…...

ubuntu非root用户操作root权限问题-virbox挂在共享文件夹
首先讲一下,virtuallbox 挂在文件夹,操作的时候总是需要root权限,比较费劲。 这一操作其实也正对着我们在Ubuntu上的操作。 前段时间我想在ubuntu正常用户下去操作i2c,也出现了类似的问题。 后来把正常的操作加到组里面也解决了类…...

网络通讯协议
层次协议应用层HTTP, HTTPS, FTP, SMTP, POP3, IMAP, DNS, DHCP, SNMP, Telnet, SSH, SIP, RTP, RTCP, TFTP, NTP, ICMP, IGMP传输层TCP, UDP网络层IP, ICMP, IGMP数据链路层Ethernet, PPP, HDLC, ATM, Frame Relay ISO/OSI 参考模型协议应用层HTTP, HTTPS, FTP, SMTP, POP3, …...

centos,789使用mamba快速安装devtools
如何进入R语言运行环境请参考:Centos7_miniconda_devtools安装_R语言入门之R包的安装_r语言devtools包怎么安装-CSDN博客 在R里面使用安装devtools经常遇到依赖问题,排除过程过于费时,使用conda安装包等待时间长等。下面演示centos,789都是一…...

【人工智能机器学习基础篇】——深入详解强化学习之常用算法Q-Learning与策略梯度,掌握智能体与环境的交互机制
深入详解强化学习之常用算法:Q-Learning与策略梯度 强化学习(Reinforcement Learning, RL)作为机器学习的一个重要分支,近年来在多个领域取得了显著成果。从棋类游戏的人机对战到自主驾驶汽车,强化学习技术展示了其强大…...

银河麒麟桌面v10sp1修复引导笔记
1.安装双系统最好备份esp分区,uefi引导丢失可以用diskgen,选择工具再点击设置uefi bios,鼠标右键选择efi文件。 2.银河麒麟界面添加windows,复制EFI/Microsoft或者pe生成引导文件后,修复Windows引导用下面命令 /桌面# update-gru…...

深入理解 MVCC 与 BufferPool 缓存机制
深入理解 MVCC 与 BufferPool 缓存机制 在 MySQL 数据库中,MVCC(Multi-Version Concurrency Control)多版本并发控制机制和 BufferPool 缓存机制是非常重要的概念,它们对于保证数据的一致性、并发性以及提升数据库性能起着关键作用…...

vue实现下拉多选、可搜索、全选功能
最后的效果就是树形的下拉多选,可选择任意一级选项,下拉框中有一个按钮可以实现全选,也支持搜索功能。 在mounted生命周期里面获取全部部门的数据,handleTree是讲接口返回的数据整理成树形结构,可以自行解决 <div c…...

探秘Kafka源码:关键内容解析
文章目录 一、以kafka-3.0.0为例1.1安装 gradle 二、生产者源码2.1源码主流程图2.2 初始化2.3生产者sender线程初始化2.4 程序入口2.5生产者 main 线程初始化2.6 跳转到 KafkaProducer构造方法 一、以kafka-3.0.0为例 打开 IDEA,点击 File->Open…->源码包解…...

Android音频效果处理:基于`android.media.audiofx`包的原理、架构与实现
Android音频效果处理:基于android.media.audiofx包的原理、架构与实现 目录 引言Android音频框架概述android.media.audiofx包简介音频效果处理的原理 4.1 音频信号处理基础4.2 常见音频效果android.media.audiofx的架构设计 5.1 类结构分析5.2 设计模式应用系统定制与扩展 6…...

LeetCode - 初级算法 数组(两个数组的交集 II)
两个数组的交集 II 这篇文章讨论如何求两个数组的交集,并返回结果中每个元素出现的次数与其在两个数组中都出现的次数一致。提供多个实现方法以满足不同场景需求。 免责声明:本文来源于个人知识与公开资料,仅用于学术交流。 描述 给定两个整数数组 nums1 和 nums2,以数…...

SQL 实战:分页查询的多种方式对比与优化
在处理大数据表时,分页查询是非常常见的需求。分页不仅可以提高用户体验,还能有效减少数据库查询返回的数据量,避免一次性加载大量记录引起的性能瓶颈。 然而,在数据量较大或复杂查询中,简单的分页方式可能导致性能下降…...
汇川Easy系列正弦信号发生器(ST源代码)
正弦余弦信号发生器CODESYS和MATLAB实现请参考下面文章链接: 正弦余弦信号发生器应用(CODESYS ST源代码+MATLAB仿真)_st语言根据输入值,形成正弦点-CSDN博客文章浏览阅读410次。本文介绍了如何在CODESYS编程环境中创建正弦和余弦信号发生器。通过详细的PLC梯形图和SCL语言代码…...

JavaSpring AI与阿里云通义大模型的集成使用Java Data Science Library(JDSL)进行数据处理
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默, 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把…...

Three.js教程002:Three.js结合Vue进行开发
文章目录 Three.js结合Vue开发创建Vue项目安装依赖运行项目安装three使用three.js完整代码下载Three.js结合Vue开发 创建Vue项目 创建命令: npm init vite@latest框架这里选择【Vue】: 安装依赖 安装命令: cd 01-vueapp npm install运行项目 npm run dev...

pycharm+anaconda创建项目
pycharmanaconda创建项目 安装: Windows下PythonPyCharm的安装步骤及PyCharm的使用-CSDN博客 详细Anaconda安装配置环境创建教程-CSDN博客 创建项目: 开始尝试新建一个项目吧! 选择好项目建设的文件夹 我的项目命名为:pyth…...

vue2中遇到的问题与解决方案(自用)
1 、在vue2中怎么能成功渲染字符串中存在自定义组件 比如,前端样式定义后由接口返回想渲染的样式,如果此时直接使用v-html,那么vue的自定义组件或者ui框架的组件是会被直接引用不能编译成功 解决方案: 此时想到vue官网使用render函…...
CF2043b-B. Digits
题目链接 题意:给定两个整数n、d,要求找出排列成n!个d之后的数可以被1-9中奇数整除的数 题解: 主要是考察分类讨论: 被3整除,当d能被3整除时一定成立或者n > 3,当n > 3时n!一定包含因数3 被5整除&a…...

ultralytics库RT-DETR代码解析
最近读了maskformer以及maskdino的分割头设计,于是想在RT-DETR上做一个分割的改动,所以选择在ultralytics库中对RTDETR进行改进。 本文内容简介: 1.ultralytics库中RT-DETR模型解析 2. 对ultralytics库中的RT-DETR模型增加分割头做实例分割 1.ultralytics库中RT-DETR模型解…...

(七)- plane/crtc/encoder/connector objects
1,framebuffer/plane Rockchip RK3399 - DRM framebuffer、plane基础知识 - 大奥特曼打小怪兽 - 博客园 2,crtc Rockchip RK3399 - DRM crtc基础知识 - 大奥特曼打小怪兽 - 博客园 3,encoder/connector/bridge Rockchip RK3399 - DRM en…...

基于STM32的四轴飞行器的控制系统(论文+源码)
1.系统设计 本次基于stm32单片机的四轴飞行器控制系统主要包括硬件和软件这两大部分,其中硬件部分是基于单片机的四轴飞行器控制系统实现的基石,其中主要STM32单片机负责整个系统功能的实现;NRF24L01无线模块负责对四轴飞行器的远程控制&…...

混合精度训练(Mixed Precision Training)中为什么在训练过程中不直接使用bf16进行权重更新?中英双语
中文版 为什么在训练过程中不直接使用 bf16 进行权重更新? 在深度学习的训练过程中,我们通常使用 混合精度训练(Mixed Precision Training)来提高训练效率,减少内存占用。虽然 bf16(Brain Floating Point…...

【java】HashMap的实现原理
目录 1. 说明2. 哈希函数3. 桶数组4. 哈希冲突解决5. 动态扩容6. 查找、插入和删除操作 1. 说明 1.HashMap是一个基于哈希表的数据结构,它实现了Map接口。2.HashMap允许使用null键和null值,并且不保证映射的顺序。 2. 哈希函数 1.HashMap使用哈希函数…...

FCM32F103C8T6开发指引
打了块板,没有STM芯片了,于是,换了块FCM32F103C8T6.原来的工程直接编译,不能仿真,提示M3,M4核不兼容,但是,用jflash是可以直接把bin文件烧录进去的,也可以正常运行起来。 但为了方便…...

Python世界:人生苦短,我用Python
Python世界:人生苦短,我用Python 前言Python优势Python缺点 前言 几句话说清,我们为啥要用Python? Python设计之初心,是为了解决编程门槛,让大家更聚焦业务实现,而非编程细节。当前人工智能火…...

【从零开始入门unity游戏开发之——C#篇43】C#补充知识——值类型和引用类型汇总补充、变量的生命周期与性能优化、值类型和引用类型组合使用
文章目录 一、值类型和引用类型汇总补充1、值类型和引用类型汇总2、值类型和引用类型的区别3、简单的判断值类型和引用类型 二、变量的生命周期与性能优化1、**栈和堆的区别**2、**变量生命周期**3、**垃圾回收(GC)机制**4、**代码示例与优化**4.1. 临时…...

从论文到实践:Stable Diffusion模型一键生成高质量AI绘画
🏡作者主页:点击! 🤖编程探索专栏:点击! ⏰️创作时间:2024年12月24日10点02分 神秘男子影, 秘而不宣藏。 泣意深不见, 男子自持重, 子夜独自沉。 论文源地址有视频: 链接h…...