RAM明明断电会丢失数据,为什么初始化的全局变量存储在RAM?详细分析程序的存储

news2024/9/27 9:27:47

前言

(1)之前因为一个字符指针和字符数组指针引发的bug,折磨了我一个下午才发现问题。之后我就打算研究一下系统是如何发现野指针乱访问问题。后面就一直深入到微机系统中的内存管理了。
(2)这些其实都是基础知识,只不过我一直不太明白,所以在此记录一下。
(3)阅读本文之前,需要对C语言的指针和动态内存管理有一定的了解

程序的存储

程序的主要存储模型

(1)一般来说,一个程序主要是由代码段常量区静态数据区这五个部分组成。而静态数据区又可以分为BSS段和数据段。
(2)代码段:负责存放程序编译后的二进制代码。有时候存在一些看起来是会存储的数据,实则上不占空间,他们是最终是成为了代码,放在了代码段。如下:
<1>#define 宏定义:在C/C++中,可以使用预处理指令#define定义常量宏。例如:#define PI 3.1415926。在编译时,预处理器会将所有的宏定义进行替换,将宏定义的常量直接嵌入到代码中。
<2>枚举(Enum):在C/C++中,使用枚举可以定义一组具有常量值的符号名称。枚举在代码中实现的功能跟使用宏类似,都可以用名字来代替数值。宏是在预处理阶段将名字换成了值,而枚举是在编译阶段将名字换成值。也就是说,枚举类型的这些名字不是变量,它们不会占据任何内存。且这些名字的有效范围是全局的,如果有变量等命名冲突了可能导致编译不过。
<3>字面常量(Literal Constants):在代码中直接使用的字面常量,例如整数、浮点数、字符常量和字符串常量。这些字面常量在编译时会直接存放在常量区。例如,if(a<5)…这个数字5就是字面常量。
(3)常量区:存放只读变量和字符串常量,一经初始化,不可修改。
<1>const修饰的全局变量:在全局作用域中使用const修饰的变量也会被存放在常量区。如果是被const修饰的局部变量,是被存放在栈上的。
<2>常量字符串:char *str2 = “hello”;中的"hello"就是一个常量字符串。
(3)静态数据区:用于存放全局变量和静态数据(前面加上了static的变量)。
<1>BSS段:用于存储未初始化或显示初始化为0的全局变量和静态变量。
<2>数据段(.data):用于存储已经初始化的全局变量和静态变量。
<3>为什么要给静态数据区分.data段和.bss段?
在程序编译的时候,不会给.bss段中的数据分配空间,只是记录数据所需空间的大小。在程序执行的时候,才会给.bss段中的数据分配内存。通过这种方式,可以节省一部分内存空间,进一步缩减可执行程序的大小。
(4):存放局部变量,函数调用信息等。由系统自动分配/释放。
(5):malloc,alloc函数申请的空间。由程序员手动申请/释放,若不手动释放,程序结束后由系统回收。

在这里插入图片描述

通过代码分析

(1)从课本上我们能够知道很多专业术语,所以说,我是不想再谈术语了,我将直接上代码理解。否则这就是水文了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//宏定义和枚举常量会被当成代码编译进代码段,所以说,不占空间
#define PI 3.1415926  
enum color
{
	red,
	green,
	yellow
};

int a = 6;  //存放在静态数据区的数据段
char* str1; //存放在静态数据区的BSS段
static int c = 3;  //存储在静态数据区的数据段
static int d;  //存储在静态数据区的BSS段
const int b = 5; //存放在常量区

int main()  //main函数本质上就是一个函数指针,存放在栈中
{
	static int e; //存储在静态数据区的BSS段
	static int f = 2; //存储在静态数据区的数据段
	const char g; //存储在栈中
	char *str2 = "hello"; //str2存放在栈中,“hello”存放在常量区
	char str3[]="world"; //str3存放在栈中
	str1 = (char*)malloc(sizeof(char)*strlen(str2)); //申请的内存存放在堆中
	strcpy(str1,str2); //进行字符串拷贝
	if(a>5);
	printf("str1 = %s\r\n",str1);
	printf("str2 = %s\r\n",str2);
	printf("str3 = %s\r\n",str3);
	return 0;
}

(2)虽然我在上面这串代码写上了注释,说明了这些东西都存放在哪里,但是还是给各位分析一波。我们首先分析定义在函数之外的变量。
<1>#define PI 3.1415926 这个宏定义,在程序预处理阶段,会将所有的PI转换为数字3.1415926 ,而3.1415926 这个数字,会被当成代码,存储在代码段。
<2>enum color{}这个枚举类型,里面的枚举常量red,green, yellow他们和宏定义类似,在程序的编译阶段,他们会被替换成对应的数字,所以说他们是不占空间的!(这个不占空间是相对的,他们是存放在了代码段,而代码段一般不算在我们所说的空间里面
<3>然后我们来看变量a,他被初始化了成了一个非0的数据,所以存放在静态数据区的数据段。
<4>而str1没有被初始化,所以他被存放在静态数据区的BSS段。
<5>同理,c是在静态数据区的数据段,d是在静态数据区的BSS段。
<6>因为b被const修饰,所以b是被存放在常量区。b是无法被再度修改了。

//宏定义和枚举常量会被当成代码编译进代码段,所以说,不占空间
#define PI 3.1415926  
enum color
{
	red,
	green,
	yellow
};

int a = 6;  //存放在静态数据区的数据段
char* str1; //存放在静态数据区的BSS段
static int c = 3;  //存储在静态数据区的数据段
static int d;  //存储在静态数据区的BSS段
const int b = 5; //存放在常量区

(3)然后我们再分析函数之内的变量。
<1>e和f虽然是被定义在了函数内部,属于局部变量,但是被static修饰,所以他们属于静态局部变量,最终是存储在静态数据区。然后根据有没有被初始化,可以区分他们到底是存储在静态数据区的BSS段,还是数据段。
<2>g虽然被const关键字修饰,但是他不是全局变量,所以依旧是和局部变量一样,保存在栈中,函数结束就会被释放。但是和局部变量的不同之处在于,g这个变量是无法被改变的。
<3>定义在函数内部变量属于局部变量。所以str2和str3是属于局部变量,而且两个数据没有被特殊关键字修饰,因此str2和str3被存放在了栈中。
<4>那么现在有一个问题,“hello”和“world”是存放在了哪里?上面说了,常量区可以存储字符串常量。所以"hello"其实是被被存放在了常量区。str2作为一个指针,可以来访问这个数据,但是不能进行篡改。(后面会讲为什么)而“world”却不一样,他是存放在栈中的。
<5>为什么呢?因为str3[]是一个字符数组,而不是和str2一样的字符指针。str3[]中存放的是数据,str2中存放的是指针。
<6>char *str2 = “hello”;对于计算机而言,就是先在常量区弄一个"hello"字符串,然后把这个字符串的首地址返还给存储在栈中的str2这个字符指针。
<7>而char str3[]=“world”;对于计算机而言,就是在栈中开辟一个字符数组空间存放"world"字符串。他并不是指针!
<8>最后malloc申请的空间,是存放在堆中的。然后存放在静态数据区的BSS段的str字符指针指向这一块堆。
<9>当我们调用strcpy(str1,str2);函数,就会读取存放在常量区的"hello"常量字符串。(注意,这里是读取,不是写入。常量区可读不可写)读取到这个字符串之后,将这个数据复制到malloc在堆申请的空间中。

int main()  //main函数本质上就是一个函数指针,存放在栈中
{
	static int e; //存储在静态数据区的BSS段
	static int f = 2; //存储在静态数据区的数据段
	const char g; //存储在栈中
	char *str2 = "hello"; //str2存放在栈中,“hello”存放在常量区
	char str3[]="world"; //str3存放在栈中
	str1 = (char*)malloc(sizeof(char)*strlen(str2)); //申请的内存存放在堆中
	strcpy(str1,str2); //进行字符串拷贝
	if(a>5);
	printf("str1 = %s\r\n",str1);
	printf("str2 = %s\r\n",str2);
	printf("str3 = %s\r\n",str3);
	return 0;
}

中断向量表

(1)在嵌入式系统中,还有一个很重要的概念,中断向量表。
(2)中断向量表是用于存储中断程序的,比如我们在写单片机程序的时候,有串口中断,定时器中断这种程序。这些程序负责存储在中断向量表中。
(3)对于同一款MCU,他的中断向量表大小和位置一般是固定的。他和代码段的存储大小相比,一般都小很多。所以说,这也是为什么经验老道的单片机攻城狮反复强调中断程序要短而精悍的原因之一了。

这些存储区与ROM和RAM的关系

ROM和RAM是什么

(1)很多人学习这一块知识的时候,经常会听到,代码存放在ROM区,代码存放在代码段。malloc申请的数据是存放在RAM,还有一种说法是malloc申请的数据就是堆。想必大家听这些东西,肯定听的云里雾里的。没错,我当时也是一样云里雾里的。
(2)所以说,为了搞清楚这个。我们就可以先搞明白ROM和RAM是什么东西。
<1>ROM,英文全称Read-Only Memory,翻译过来就是只可以读取的存储器。从名字上就能够很清楚的知道ROM是一个只可以读取的存储器,但是我们需要注意的一点是,这个只可以读取是针对于CPU而言的,人们可以通过一些外部手段进行写入数据。ROM的存储特性比较稳定,他断电之后也不会丢失数据。所以说,ROM是一个存储稳定(断电不丢失数据),对CPU而言只可读的存储器。
<2>RAM,英文全称Random Access Memory,翻译过来就是随机存取存储器。从随机两个字就可以知道,这个存储器并不稳定,所以他断电就会丢失数据。存储两个字可以知道,这个存储器CPU是可以对他进行写操作的。所以说,RAM是一个可读可写,但是存储不稳定的存储器。(注意,这个不稳定是指,断电之后数据会丢失,但是如果你通电了,数据是不会出现问题的!)
<3>ROM和RAM不但从存储的稳定性和读写权限上有区别,从数据的读取速度上也有区别。RAM的数据读取速度是比ROM要高的

ROM与Flash是什么关系

(1)上个世纪,发明了两种存储器,一个是RAM,一个是ROM。当时的ROM一旦存储数据就再也无法将之改变或者删除,也就是说,程序只有一次下载机会。如果你程序写错了,那么这个芯片就报废了。很明显非常的鸡肋。
(2)为了便于使用和大批量生产,进一步发展出了可编程只读存储器(PROM)、可擦除可编程只读存储器(EPROM)。EPROM需要用紫外线长时间照射才能擦除,使用很不方便。
(3)1980s又出现了电可擦除可编程只读存储器(EEPROM),它克服了EPROM的不足,但是集成度不高、价格较贵。
(4)于是又发展出了一种新型的存储单元结构同EPROM类似的快闪存储器(FLASH MEMORY)。FLASH集成度高、功耗低、体积小,又能在线快速擦除,因而获得了快速发展。关系图如下

在这里插入图片描述

(5)因此,现在的单片机存储数据主要是使用的Flash,就是替代以前的ROM,最大的有有点是降低了芯片的成本并且可以做到电擦写。但是因为最先发展出来的是ROM,所以很多时候,人们常常愿意说芯片里面的是ROM存储代码,而不是说Flash存储代码。我们需要了解到这个即可,随便你怎么讲。我们只需要知道,现在的芯片都是用Flash存储数据,但是也要能够明白,很多人讲的ROM就是Flash

ROM,RAM和以上的哪些存储结构体关系

(1)现在我们知道了ROM和RAM是什么之后,那么他们和上面的存储结构有什么联系呢?
<1>中断向量表:我们的中断程序肯定是要存放在ROM中的,为什么呢?假设,中断程序存储在RAM中,会发生什么。你系统一断电,辛辛苦苦写的代码,没了。你想想有多崩溃。
<2>代码段:代码段和中断向量表是一样的存储在ROM区域。但是,可以通过特殊方法让代码段存储在RAM中。为啥要这么做呢?上面说了RAM的数据读取速度要比ROM高,如果让代码段放在RAM中,那样代码的执行效率会有提高。(注意,这个提高是需要一定的数据量,之前我在B站看到一个人测试代码放在ROM和RAM中跑的效果差不多,是因为数据量太少了,效果不明显)
这个时候肯定有细心的同学会问了。不对啊,你代码段放在RAM中,断电所有程序不就没有了吗?对的,这个就是我们后面要讲解的通电和断电状态下的数据存储。我们上面讨论的,都是通电状态下的数据存储!
<3>常量区:存储在ROM,因为常量区的数据不能被修改,而且ROM的比较大,所以正好合适。
<4>静态数据区:存储在RAM,因为这些变量我们会要经常使用到,而且还会进行更改。
<5>:存储在RAM,函数的调用和局部变量的申请和释放是都是变化的,而且里面的数据我们会进行调整。所以要放在RAM。
<6>:存储在RAM,malloc和alloc申请的空间,是要被使用的,他们会要存储数据,所以要放在RAM中。

断电和通电状态下的数据存储

为什么要分断电和通电状态分析

(1)在一款MCU中,一个程序编译完成之后,需要烧录进去。这个程序有代码段,只读数据,可读可写数据。上文我们分析了,代码段和只读数据存储在ROM,那么可读可写的数据存储在哪里呢?难道是RAM吗?
(2)可能有人认为我自相矛盾,上面都说了,可读可写的数据放在RAM,这里还反问。一塌糊涂!可是,大家认真思考一下,这些可读可写的数据有被赋予初值的变量。假设,这些变量存储在RAM,会发生什么?
(3)很明显,这些被赋予初值的变量存储在RAM,那么一旦断电了,这些初值将会消失!也就是说,我们的这些初值没了!
(4)由此可见,我们数据存储要分成断电和通电两种状态进行分析。而我们上面讲了那么多,其实都是以通电状态进行数据存储的分析的。

断电状态数据存储

(1)从上面的分析我们可以知道,虽然在通电状态下,可读可写数据是要存储在RAM的,但是在断电状态还是要存储在ROM的。所以,现在我就以STM32的断电和通电存储来进行讲解。
(2)我们对一个STM32的工程文件进行编译,会出现如下的编译结果。其中有一行是Program Size: Code=5984 RO-data=336 RW-data=56 ZI-data=1024 。
(3)
<1>Code : 代码段,存放程序的代码部分。
<2>RO-data:只读数据段,存放程序中定义的常量。
<3>RW-data:读写数据段,存放初始化为非 0 值的全局变量。
<4>ZI-data:零初始化的可读写变量,存放未初始化的全局变量及初始化为 0 的变量。

在这里插入图片描述

(4)如上为最终我们编写的代码产生的四类数据。但是这四类数据都会存储在STM32的ROM中吗?这个时候我们可以双击工程文件文件——>出现.map文件——>滑动到最后——>看Total ROM Size,这个才是最终下载进入STM32的数据。
(5)细心的朋友会发现,怎么这个Total ROM Size = Code + RO Data + RW Data。ZI-data去哪里?
(6)我们上面在讲解静态数据区的时候,看到静态数据区被分成了BSS段和数据段。为什么需要给静态数据区进行分区,给出的解释是,在程序编译的时候,不会给.bss段中的数据分配空间,只是记录数据所需空间的大小。在程序执行的时候,才会给.bss段中的数据分配内存。
(7)可能又有人要说了,我是怎么记录数据所需空间大小的呢?这个就和编译器有关了,我查阅了一些资料后发现这个与编译器的符号表有关。但是符号表最终不会存储进入芯片。所以,我个人猜测,程序需要访问一个变量,首先需要知道他的地址。所以说,这个地址最终存储进入代码中,然后对这个地址进行访问,因此不需要占空间。而被初始化的数据则不同,我就算知道了访问的地址,而且知道要访问多大数据,但是不知道他的初始值。所以我们需要将这个初始值存放在ROM中。在程序加载的阶段将这个初始值存入对应的RAM。

在这里插入图片描述

(8)因此,我们知道,在断电状态,数据都存储在了ROM中,如下。(注意,这张图没有出现Code,但是我们还是需要知道Code数据存储在ROM中)

在这里插入图片描述

通电状态数据存储

(1)通过上面的分析我们知道了断电和通电状态下的数据存储了。那么上电的一瞬间发生了什么?
(2)RO-Data数据不变,RW-Data的数据被复制到RAM区域,RAM中划分出一个ZI-data区域。当程序执行过程中,被初始化为0的数据,CPU会向ZI-data对应的变量中写入0。没有被初始化的数据,那么就是随机值。这也是为什么我们初学C语言的时候,反复强调要进行初始化。但是keil帮我们做了很多事情,如果你变量没有被初始化,他在划分ZI-data区域的时候,会把这个区域全部清零。

在这里插入图片描述

(3)RO-Data的数据就是常量区的数据,RW-Data对应静态区的数据段,ZI-data对应静态区的BSS段。那么堆栈呢?
(4)堆栈是由启动文件划分的,如果你感兴趣,可以打开STM32的启动你文件startup_stm32f10x_hd.s。他可以看到他第一部分就是设置堆栈信息的,所以堆栈是由启动文件来划分。

;1-栈
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> 栈配置,用于变量,函数调用
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400    ; 1KB

                AREA    STACK, NOINIT, READWRITE, ALIGN=3 ;告诉汇编器汇编一个新的代码段或者数据段,名字叫做STACK,不初始化,可读可写,以8字节对齐
Stack_Mem       SPACE   Stack_Size  ;对应EQU的那一行,表示分配1KB的空间
__initial_sp

;2-堆                                                  
; <h> 堆配置,用于malloc等函数的动态内存分配
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200   ;512B

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3 ;告诉汇编器汇编一个新的代码段或者数据段,名字叫做HEAP,不初始化,可读可写,以8字节对齐
__heap_base  ;堆的起始地址
Heap_Mem        SPACE   Heap_Size ;对应EQU的那一行,表示分配512B的空间
__heap_limit

                PRESERVE8    ;当前堆栈8字节对齐
                THUMB        ;兼容 THUMB 指令,老的指令,16bit,现在Cortex-M3的都是THUMB-2指令,兼容16/32位

参考文章

(1)RAM、ROM、FLASH 有什么不同?
https://bbs.elecfans.com/jishu_1404990_1_3.html
(2)STM32 KEIL里的MAP文件分析:
https://blog.csdn.net/qlexcel/article/details/78884379
(3)谈谈内存分配中的(.data)段和(.bss)段的区别?为什么要分.data段和.bss段?
https://blog.csdn.net/Ivan804638781/article/details/110209548
(4)请描述一个可执行程序占用的内存分为哪几个区?每个分区各自的作用?
https://blog.csdn.net/Ivan804638781/article/details/110010286
(5)ROM, FLASH和RAM的区别:
https://zhuanlan.zhihu.com/p/38339306
(6)单片机FLASH与RAM、ROM的关系:
https://zhidao.baidu.com/question/151273888.html
(7)STM32单片机程序的存储和执行:
https://mp.weixin.qq.com/s/p5oa0oZLN4Aa1QjZofBYcA

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

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

相关文章

SpringBoot房屋租赁系统【附ppt|万字文档(LW)和搭建文档】

主要功能 前台登录&#xff1a; ①首页&#xff1a;公告信息、房屋信息展示、查看更多等 ②房屋信息、房屋类型、我要当房主、公告信息、留言反馈等 ③个人中心&#xff1a;可以查看自己的信息、更新图片、更新信息、退出登录、我的收藏 后台登录&#xff1a; ①首页、个人中心…

Day 69-70:矩阵分解

代码&#xff1a; package dl;import java.io.*; import java.util.Random;/** Matrix factorization for recommender systems.*/public class MatrixFactorization {/*** Used to generate random numbers.*/Random rand new Random();/*** Number of users.*/int numUsers…

使用贝叶斯算法完成文档分类问题

贝叶斯原理 贝叶斯原理&#xff08;Bayes theorem&#xff09;是一种用于计算条件概率的数学公式。它是以18世纪英国数学家托马斯贝叶斯&#xff08;Thomas Bayes&#xff09;的名字命名的。贝叶斯原理表达了在已知某个事件发生的情况下&#xff0c;另一个事件发生的概率。具体…

【Golang系统开发】搜索引擎(1) 如何快速判断网页是否已经被爬取

文章目录 1. 写在前面2. 数组存储3. 位图存储3.1 位图简介3.2 链表法3.3 开放寻址法 1. 写在前面 在实际工作中&#xff0c;我们经常需要判断一个对象是否存在&#xff0c;比如判断用户注册登陆时候&#xff0c;需要判断用户是否存在&#xff0c;再比如搜索引擎中的爬虫&#x…

大数据面试题之Elasticsearch:每日三题(七)

大数据面试题之Elasticsearch:每日三题 1.Elasticsearch索引文档的流程&#xff1f;2.Elasticsearch更新和删除文档的流程&#xff1f;3.Elasticsearch搜索的流程&#xff1f; 1.Elasticsearch索引文档的流程&#xff1f; 协调节点默认使用文档ID参与计算(也支持通过routing)&a…

【UE4】给角色添加脚步声

步骤&#xff1a; 1. 导入一个脚步声音频文件&#xff08;.wav格式&#xff09; 2. 打开角色蓝图&#xff0c;这里以第三人称角色模板蓝图“ThirdPersonCharacter”为例&#xff0c;在事件图表中添加一个生成音效的自定义事件。 3. 打开动画序列“ThirdPersonRun” 找到小白人…

Python编程——while循环嵌套讲解(附案例)

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 本文专栏&#xff1a;Python专栏 专栏介绍&#xff1a;本专栏为免费专栏&#xff0c;并且会持续更新python基础知识&#xff0c;欢迎各位订阅关注。 目录 一、while嵌套的语法 二、注意事项 三、while嵌套循…

【Java】分支结构习题

【Java】分支结构 文章目录 【Java】分支结构题1 &#xff1a;数字9 出现的次数题2 &#xff1a;计算1/1-1/21/3-1/41/5 …… 1/99 - 1/100 的值。题3 &#xff1a;猜数字题4 &#xff1a;牛客BC110 X图案题5 &#xff1a;输出一个整数的每一位题6 &#xff1a; 模拟三次密码输…

统信UOS安装mysql数据库(mariadb)-统信UOS安装JDK-统信UOS安装nginx(附安装包)

统信UOS离线全套安装教程&#xff08;手把手教程&#xff09; 银河麒麟的各种离线全套安装教程&#xff1a; https://blog.csdn.net/ACCPluzhiqi/article/details/131988147 1.统信UOS桌面系统安装mysql&#xff08;mariadb&#xff09; 2.统信UOS桌面系统安装JDK 3.统信UOS桌…

CAN学习笔记3:STM32 CAN控制器介绍

STM32 CAN控制器 1 概述 STM32 CAN控制器&#xff08;bxCAN&#xff09;&#xff0c;支持CAN 2.0A 和 CAN 2.0B Active版本协议。CAN 2.0A 只能处理标准数据帧且扩展帧的内容会识别错误&#xff0c;而CAN 2.0B Active 可以处理标准数据帧和扩展数据帧。 2 bxCAN 特性 波特率…

【JavaWeb】正则表达式

&#x1f384;欢迎来到边境矢梦的csdn博文&#xff0c;本文主要讲解Java 中正则表达式 的相关知识&#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一下&#x1faf0;&#x1faf0;&am…

Metabase RCE漏洞复现(CVE-2023-38646)

0x01 产品简介 Metabase是一个开源的数据分析和可视化工具&#xff0c;它可以帮助用户轻松地连接到各种数据源&#xff0c;包括数据库、云服务和API&#xff0c;然后使用直观的界面进行数据查询、分析和可视化。 0x02 漏洞概述 未经身份认证的远程攻击者利用该漏洞可以在服务器…

【C语言趣味教程】(4) 变量:代码注释 | 变量的声明 | 初始化与赋值 | 变量的命名 | 关键字 | 标识符 | 变量名的命名规范

&#x1f517; 《C语言趣味教程》&#x1f448; 猛戳订阅&#xff01;&#xff01;&#xff01; Ⅰ. 代码注释&#xff08;Comment&#xff09; 0x00 引入&#xff1a;注释的作用 "程序员最讨厌两种人&#xff1a;一种是不写注释的人&#xff0c;一种是让我写注释的人。…

如何用DHTMLX组件为Web应用创建甘特图?(一)

dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的所有需求&#xff0c;是最完善的甘特图图表库。甘特图仍然是项目管理应用程序中最需要的工具之一&#xff0c;DHTMLX Gantt组件提供了能提升研发甘特图功能所需的重要工具。 在这篇…

【深度学习】InST,Inversion-Based Style Transfer with Diffusion Models,论文

代码&#xff1a;https://github.com/zyxElsa/InST 论文&#xff1a;https://arxiv.org/abs/2211.13203 文章目录 AbstractIntroductionRelated WorkImage style transferText-to-image synthesisInversion of diffusion models MethodOverview ExperimentsComparison with Sty…

我的第一个后端项目(环境搭建,Springboot项目,运行,接口验证)

一. 安装Java开发工具包&#xff08;JDK&#xff09;&#xff1a; 访问Java Software | OracleOracle官方网站&#xff0c;下载适合你操作系统的最新版本的JDK。安装JDK并设置好JAVA_HOME环境变量。 二. 安装集成开发环境&#xff08;IDE&#xff09;&#xff1a; 推荐使用In…

Appium+python自动化(二十八)- 高级滑动(超详解)

高级溜冰的滑动 滑动操作一般是两点之间的滑动&#xff0c;这种滑动在这里称其为低级的溜冰滑动&#xff1b;就是上一节给小伙伴们分享的。然而实际使用过程中用户可能要进行一些多点连续滑动操作。如九宫格滑动操作&#xff0c;连续拖动图片移动等场景。那么这种高级绚丽的溜…

word里的页码问题

一份文档写完&#xff0c;如果需要页码&#xff0c;第一页是封面&#xff0c;封面不需要页码怎么办&#xff1f; 解决&#xff1a;打开页眉页脚&#xff0c;然后把首页不同勾选上&#xff0c;这一页就没有页码了。 目录页&#xff0c;往往要使用罗马数字&#xff0c;其他正文又…

《遗留系统现代化》读书笔记(模式篇-代码现代化(一))

本文地址&#xff1a;http://t.csdn.cn/vS0Za 文章目录 代码现代化&#xff1a;你的代码可测吗&#xff1f;你的代码可测吗&#xff1f;标题如何让代码变得可测&#xff1f;接缝的位置接缝的类型新生和外覆为代码添加测试决策表模式测试的类型和组织遗留系统中的测试策略 代码…

【深度学习笔记】Softmax 回归

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…