第3章“程序的机器级表示”:控制

news2024/11/24 23:01:48

文章目录

  • 3.6 控制
    • 3.6.1 条件码
    • 3.6.2 访问条件码
    • 3.6.3 跳转指令及其编码
    • 3.6.4 翻译条件分支
    • 3.6.5 循环
      • do-while 循环
      • while循环
      • for循环
    • 3.6.6 switch 语句

3.6 控制

截止目前,考虑了 访问数据操作数据 的方法。程序执行的另一个很重要的部分就是控制被执行操作的顺序。对 C 和汇编代码中的语句,默认的方式是顺序的控制流,按照语句或指令在程序中出现的顺序来执行。C中的某些程序结构,比如条件语句、循环语句和分支语句,允许控制按照非顺序方式进行,即根据程序数据的值来确定顺序。

汇编代码提供了实现非顺序控制流的较低层次的机制。基本操作是跳转到程序的另一部分,可能会视某些测试结果而定。编译器产生的指令序列是依赖于这些低层机制来实现 C 的控制结构。

下文的讲解中,会先谈到机器级机制,然后会给出如何用它们来实现 C 的各种控制结构。

3.6.1 条件码

除了整数寄存器,CPU 还包含一组单个位的条件码(condition code) 寄存器,它们描述了最近的算术或逻辑操作的属性。对这些寄存器的检测,将有助于执行条件分支指令。最有用的条件码是:

  • CF:进位标志。最近的操作使最高位产生了进位,它可用来检查无符号操作数的溢出。
  • ZF:零标志。最近的操作得出的结果为0。
  • SF:符号标志。最近的操作得到的结果为负数。
  • OF:溢出标志。最近的操作导致一个二进制补码溢出——正溢出或负溢出。

比如,用 addl 指令完成等价于 C 表达式 t = a + b 的功能,这里变量a、b 和 t 都是整型的。然后,会根据下面的表达式来设置条件码:

在这里插入图片描述
leal 指令不改变任何条件码,因为它是用来进行地址计算的。另一方面,图3.7中列出的所有指令都会设置条件码。对于逻辑操作,例如 xorl,进位标志和溢出标志会设置成0。对于移位操作,进位标志将设置为最后一个被移出的位,而溢出标志设置为0。

除了图3.7 中的操作,下面的表给出了两个操作(有8、16 和 32 位形式),它们只设置条件码而不改变任何其他寄存器。

在这里插入图片描述

cmpbcmpwcmpl 指令根据它们的两个操作数之差来设置条件码。在 GAS 格式中,操作数的顺序是相反的,使得代码有点难读。如果两个操作数相等,这些指令会将零标志设置为1,而其他的标志可以用来确定两个操作数之间的大小关系。

testbtestwtestl 指令会根据它们的两个操作数的与(AND)来设置零标志和负数标志。通常两个操作数是一样的(例如,testl %eax,%eax 用来检查 %eax 是负数、零还是正数),或其中的一个操作数是用来指示哪些位应该被测试的掩码。

3.6.2 访问条件码

条件码通常不会直接读取,两种最常用的访问条件码的方式是根据条件码的某个组合,设置一个整数寄存器或是执行一条条件分支指令。

下图3.10中描述的是各种 set 指令根据条件码的各个组合,将一个字节设置为0或者1。目的操作数是八个单字节寄存器元素之一,或是存储一个字节的存储器位置。为了得到一个 32 位结果,必须对最高的 24 位清零。

在这里插入图片描述
一个 C 判定条件(如 a < b a<b a<b)的典型指令序列如下所示:

在这里插入图片描述
movzbl 指令用来清零三个高位字节。

某些底层的机器指令可能有多个名字,称之为 “同义名(synonym)”。比如说,“setg”(表示“设置大于”)和 “setnle”(表示“设置不小于等于”)指的就是同一条机器指令。编译器和反汇编器会随意决定使用哪个名字。

虽然所有的算术操作都会设置条件码,但是各个 set 命令的描述都适用于这样一种情况:执行比较指令,根据计算 t = a − b t = a -b t=ab 设置条件码。例如,就 sete 来说,即“当相等时设置(Set when equal)” 指令。当 a = b a = b a=b 时,会得到 t = 0 t = 0 t=0,因此零标志置位就表示相等。

setl 测试有符号比较

类似地,考虑用 setl,即 “当小于时设置(Set when less)” 指令,测试一个有符号比较。当 a a a b b b 是用二进制补码表示时,对于 a < b a<b a<b,计算两者之差时,会有 a − b < 0 a-b<0 ab<0

  • 当没有溢出发生时,符号标志置位就表明 a < b a<b a<b
  • 当因为 a − b a-b ab 是一个很大的正数,出现正溢出时,会得到 t < 0 t<0 t<0
  • 当因为 a − b a-b ab 是一个很小的负数,出现负溢出时,得到 t > 0 t>0 t>0

无论这两种情况中的哪一种,符号标志都表示的是真正的差的反。因此,溢出和符号位的异或测试的就是 a < b a<b a<b。其他的有符号比较测试是基于 SF ^ OFZF 的其他组合。

无符号比较的测试

对于无符号比较的测试,当无符号参数 a a a b b b 的整数差是负数时,也就是当 (unsigned)a < (unsigned) b 时,cmpl 指令会设置进位标志。因此,这些测试使用的是进位标志和零标志的组合。

3.6.3 跳转指令及其编码

正常执行的情况下,指令按照它们出现的顺序一条一条地执行。跳转(jump)指令会导致执行切换到程序中一个全新的位置(如下图3.11所示)。这些跳转的目的地通常用一个标号(label)指明。
在这里插入图片描述

考虑下面这样的汇编代码序列:
在这里插入图片描述
指令 jmp .L1 会导致程序跳过 movl 指令,从 popl 指令开始继续执行。在产生目标代码文件时,汇编器会确定所有带标号指令的地址,并使跳转目标(目的指令的地址)编码为跳转指令的一部分。

jmp 指令是无条件跳转。它可以是直接跳转,即跳转目标是作为指令的一部分编码的,也可以是间接跳转,即跳转目标是从寄存器或存储器位置中读出的。 汇编语言中,直接跳转是给出一个标号作为跳转目标的,例如,上面代码中的标号 “.L1”。间接跳转的写法是 “*” 后面跟一个操作数指示符,语法与 movl 指令使用的一样。看如下例子,指令

jmp *%eax

用寄存器 %eax 中的值作为跳转目标,而指令

jmp *(%eax)

以 %eax 中的值作为读地址,从存储器中读出跳转目标。

其他的跳转指令是根据条件码的某个组合,或者跳转,或者继续执行代码序列中下一条指令。请注意这些指令的名字和跳转条件与 set 指令是相匹配的。同 set 指令一样,一些底层的机器指令有多个名字。条件跳转只能是直接跳转。

在汇编代码中,跳转目标是用符号标号书写的。汇编器,以及后来的链接器,会产生跳转目标的适当编码。跳转指令有几种不同的编码,但是最常用的一些是 PC 相关的(PC-relative,PC = Program Counter)。也就是,它们会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。这些地址偏移量可以编码为一、二或四个字节。第二种编码方法是给出“绝对”地址,用四个字节直接指定目标。汇编器和链接器会选择适当的跳转目的编码。

如下是一个与 PC 相关的寻址的例子,下面这个汇编代码的片段是编译 silly.c 文件所产生的。它包含两个跳转:第1行的 jle 指令前向跳转到更高的地址,而第8行的 jg 指令后向跳转到较低的地址。

在这里插入图片描述
注意,第2行是一条针对汇编器的命令(directive),它会使后面指令的地址从 16 的倍数处开始,而最多浪费 7 个字节。这条命令是为了使处理器能更优化地使用指令高速缓存存储器(instruction cache memory)。

汇编器产生的 “.o” 格式的反汇编版本如下:
在这里插入图片描述在这里插入图片描述
第2行的 lea 0x0(%esi),%esi 指令没有什么实际的效果。它是作为 6 个字节的空指令(nop),使得下一条指令(第3行)的起始地址是 16 的倍数。

右边反汇编器产生的注释中,指令 1 的跳转目标明确指明为 0x1b,指令7的是 0x10。不过,观察指令的字节编码,会看到跳转指令 1 的目标编码(在第二个字节中)为 0x11(十进制17)。把它加上 0xa(十进制10),也就是下一条指令的地址,就得到跳转目标地址0x1b(十进制27),也就是指令 8 的地址。

类似地,跳转指令7的目标用单字节、二进制补码表示编码为 0xf5(十进制-11)。将这个数加上 0x1b(十进制27),即指令 8 的地址,得到 0x10(十进制16),即指令3的地址。

正如这些例子说明的那样,当执行与 PC 相关的寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。 这种惯例可以追溯到早期的实现,当时,处理器会将更新程序计数器作为执行一条指令的第一步。

如下是链接后的程序反汇编的版本:

在这里插入图片描述
这些指令被重定位到不同的地址,但是第 1 行和第 7 行中跳转目标的编码并没有变。通过使用与 PC 相关的跳转目标编码,指令编码很简洁(只需要两个字节),而且目标代码可以不做改变就移到存储器中不同的位置。

3.6.4 翻译条件分支

C中的条件语句是用有条件和无条件跳转结合起来实现的。

如下给出了一个计算两数之差绝对值的函数的C代码:

原始的C代码

//原始的 C 代码
int absdiff(int x, int y)
{
	if (x < y)
		return y - x;
	else 
		return x - y;
}

GCC 产生的汇编代码

	movl 8(%ebp), %edx      Get x
	movl 12(%ebp), %eax		Get y
	cmpl %eax, %edx			Compare x:y
	jl .L3					If <, goto less
	subl %eax, %edx			Compute x-y
	movl %edx, %eax			Set as return value
	jmp .L5					Goto done
.L3:				 less:
	subl %edx, %eax			Compute y-x as return value
.L5:					done: Begin completion code				

汇编代码实现首先比较两个操作数(第3行),设置条件码。如果比较的结果表明 x 小于 y,那么它就会跳转到计算 y-x 的代码块(第9行),否则就继续执行计算 x-y 的代码(第5行和第6行)。在这两种情况下,计算结果都存放在寄存器 %eax 中,到第 10 行结束,在此,它会执行栈完成代码(没有显示出来)。

C中的 if-else 语句的通用形式是这样的:

if (test-expr)
	then-statement
else
	else-statement

这里 test-expr 是一个整数表达式,它的取值为0(解释为“假”)或者为非0(解释为“真”)。两个分支语句中(then-statement 和 else-statement)只会执行一个。

对于这种通用形式,汇编实现通常会使用下面这种形式,这里,用C语法来描述控制流:

	t = test-expr;
	if (t)
		goto true;
	else-statement
	goto done;
true:
	then-statement
done:

也就是,汇编器为 then-statement 和 else-statement 产生各自的代码块,并插入条件和无条件分支,以保证能执行正确的代码块。

与汇编代码等价的 C 版本,更加紧密地遵循汇编代码的控制流。

int gotodiff(int x, int y)
{
	int rval;
	
	if (x < y)
		goto less; 
	rval = x - y;
		goto done;
	less:
		rval = y - x;
	done:
		return rval;
}

该代码中使用了C中的goto语句,这个语句类似于汇编代码中的无条件跳转。第6行的 goto less 语句会导致一个跳转,转移到底9行的标号less处,略过了第7行上的语句。请注意,通常认为使用 goto 语句是一种不好的编程风格,因为它会使代码难以阅读和调试。在讲解中使用 goto 语句,是为了构造描述汇编代码程序控制流的C程序。我们称这样的 C 程序为 “goto代码”。

3.6.5 循环

C提供了好几种循环结构,即whilefordo-while。汇编中没有相应的指令存在。作为替代,将条件测试和跳转组合起来实现循环的效果。有趣的是,大多数汇编器根据一个循环的 do-while 形式来产生循环代码,即使在实际程序中,这种形式用的相对较少。其他的循环会首先转换成 do-while 形式,然后编译成机器代码。

do-while 循环

do-while 语句的通用形式是这样的:

do
	body-statement
	while(test-expr);

循环的效果就是重复执行 body-statement,对 test-expr 求值,如果求值的结果为非零,就继续循环。注意,body-statement至少执行一次。

通常,do-while 的实现有下面这样的通用形式:

loop:
	body-statement
	t = test-expr;
	if (t)
		goto loop;

举个例子,Fibonacci 序列的递归定义:
F 1 = 1 F 2 = 1 F n = F n − 1 + F n − 2 , n ≥ 3 F_1 = 1 \\ F_2 = 1 \\ F_n = F_{n-1} + F_{n-2}, n \ge 3 F1=1F2=1Fn=Fn1+Fn2,n3
比如说,该序列的前 10 个元素是1、1、2、3、5、8、13、21、34和55。用 do-while 循环来实现,序列从 F 0 = 0 F_0 = 0 F0=0 F 1 = 1 F_1 = 1 F1=1 开始,而不是从 F 1 F_1 F1 F 2 F_2 F2 开始的。

int fib_dw(int n)
{
	int i = 0;
	int val = 0;
	int nval = 1;
	
	do {
		int t = val + nval;
		val = nval;
		nval = t;
		i++;
	}while (i < n);

	return val;
}

对应的汇编语言代码:

在这里插入图片描述
图中显示了实现这个循环的汇编代码,以及一张列出寄存器和程序值之间对应关系的表。在这个例子中,body-statement 是第 8 ~ 11 行,对 t t t v a l val val n v a l nval nval 赋值,并将 i i i 加1。这些功能是由汇编代码的第 2 ~ 5 行实现的。表达式 i < n i<n i<n 就是 test-expr。第 6 行和第 7 行的跳转指令的测试条件实现了这个表达式。一旦退出循环,就会将 v a l val val 拷进寄存器 %eax,作为返回值(第 8 行)。

while循环

while语句的通用形式是这样的:

while(test-expr)
	body-statement

它与 do-while 的不同之处在于对 test-expr 求值,在第一次执行 body-statement 之前,循环就可能中止了。直接翻译成使用 goto 语句的形式就是:

loop:
	t = test-expr;
	if (!t)
		goto done;
	body-statement
	goto loop;
done:

这种翻译要求内循环,也就是执行次数最多的代码部分,里面有两条控制语句。相反,大多数 C 编译器将这段代码转换成 do-while 循环,用一个条件分支来在需要时省略循环体的第一次执行:

if(!test-expr)
	goto done;
do
	body-statement
	while (test-expr);
done:

然后,这段代码可以转换成带 goto 语句的代码:

  t = test-expr;
  if (!t)
	  goto done;
loop:
  body-statement
  t = test-expr;
  if (t)
    goto loop;
done:

作为一个例子,下图给出了一个用 while 循环实现的 Fibonacci 序列函数的代码。
在这里插入图片描述
在这里插入图片描述

注意,这次的递归从元素 F 1 ( v a l ) F_1(val) F1(val) F 2 ( v a l ) F_2(val) F2(val) 开始。旁边的 C 函数 fib_w_goto(b) 表明了这段代码是如何翻译成汇编的,而© 中的汇编代码非常接近于 fib_w_goto 中的 C 代码。

编译器进行了几个非常有趣的优化,可以在 goto 代码(b) 中看到。

  • 首先,编译器不是使用变量 i i i 作为循环变量并且在每次重复时拿它与 n n n 做比较,而是引入了一个新的 n m i nmi nmi 循环变量,与原来的代码相比,它的值等于 n − i n-i ni。这使得编译器只用三个寄存器作为循环变量,而不用四个。
  • 其次,它将最原始的测试条件 ( i < n ) (i<n) (i<n) 优化成了 ( v a l < n ) (val<n) (val<n),因为 i i i v a l val val 的初始值都是 1。这样一来,编译器就能完全消除变量 i i i 了。编译器常常利用变量的初始值来优化初始的测试,不过这使得解读汇编代码有点麻烦。
  • 第三,为了循环的连续执行,要保证 i ≤ n i\le n in,这样编译器就能假设 n m i nmi nmi 是非负的了。因此,它就能将 n m i ! = 0 nmi !=0 nmi!=0 而不是 n m i > = 0 nmi >=0 nmi>=0 作为循环条件来测试了。这样就在汇编代码中省略了一条指令。

for循环

for 循环的通用形式:
在这里插入图片描述
C语言标准说明,这样一个循环的行为与下面这段使用 while 循环的代码的行为是一样的:
在这里插入图片描述
即,程序首先会对初始表达式 init-expr 求值。然后进入循环,它会先对测试条件 test-expr 求值,如果测试结果为 “假” 就会退出,然后执行循环体 body-statement,最后对更新表达式 update-expr 求值。

这段代码编译后的形式是基于前面讲过的从 while 到 do-while 的转换的, 首先给出do-while 形式:
在这里插入图片描述
然后,将它转换成 goto 代码:
在这里插入图片描述
举个例子,如下给出了一个使用 for 循环的 Fibonacci 函数的实现:
在这里插入图片描述
将这段代码转换成 while 循环形式得到的代码与前文中图3.14给出的 fib_w的代码一样。实际上,GCC 对两个函数产生的汇编代码就是一样的。

3.6.6 switch 语句

switch语句提供了根据一个整数索引值进行多重分支(multiway branching)的能力。在处理具有多种可能结果的测试时,这种语句特别有用。

它们不仅提高了 C 代码的可读性,而且通过使用一种称为 跳转表(jump table) 的数据结构使得实现更加高效。跳转表是一个数组,表项 i i i 是一个代码段的地址,这个代码段实现的是当开关索引值等于 i i i 时程序应该采取的动作。程序代码用开关索引值来执行一个跳转表内的数组引用,确定跳转指令的目标。和使用一组很长的 if-else 语句相比,使用跳转表的优点是执行开关语句的时间与开关情况(switch cases)的数量无关。GCC 根据开关情况的数量和开关情况值的稀少程度(sparsity)来翻译开关语句。当开关情况数量比较多(如四个或更多),并且值的范围跨度比较小时,就会使用跳转表。

下面的 (a) 给出了一个 C switch 语句的示例。该例子有些特别的特征,包括情况标号(case lables)是不连续的(对于情况 101 和 105 是没有标号的),有些情况有多个标号(情况104 和 106),而有些情况则会落入其他情况(情况102),因为对应该情况的代码段没有以 break 语句结尾。

//(a) switch语句
int switch_eg(int x)
{
	int result = x;

	switch (x) {
		case 100:
			result *= 13;
			break;
	
		case 102:
			result += 10;
			/* Fall through */
	
		case 103:
			result += 11;
			break;
	
		case 104:
		case 106:
			result *= result;
			break;
	
		default:
			result = 0;
	}

	return result;
}
//(b) 到扩展C的翻译
// 到扩展C的翻译给出了跳转表 jt 的结构,以及是如何访问它的。实际上C中是不允许这样的表和访问的。
/*Next line is not legal C */
code *jt[7] = {
	loc_A, loc_def, loc_B, loc_C,
	loc_D, loc_def, loc_D
};

int switch_eg_impl(int x)
{
	unsigned xi = x - 100;
	int result = x;

	if (xi > 6)
		goto loc_def;
	/*Next goto is not legal C */
	goto jt[xi];

loc_A: /* Case 100 */
	result *= 13;
	goto done;

loc_B: /*Case 102 */
	result += 10;
	/* Fall through */

loc_C:	/*Case 103 */
	result += 11;
	goto done;

loc_D: /*Case 104, 106 */
	result *= result;
	goto done;

loc_def: /*Default case */
	result = 0;

done:
	return result;
}

下图3.16是编译 switch_eg 时产生的汇编代码。这段代码的行为用 C 的扩展形式来描述就是上面(b) 中的过程 switch_eg_impl。我们说“扩展的” 是因为 C 本身并不提供支持这种跳转表所需的结构,因此我们的代码并不是合法的C。数组 jt 包含 7 个表项,每个都是一个代码块的地址。为此,我们扩展了C,增加了数据类型 code。

在这里插入图片描述
在这里插入图片描述

  • 第1~4行建立起了跳转表的入口。为了保证当 x 的值小于 100 或 大于 106 时会执行 default 开关情况指定的计算,代码生成了一个等于 x-100 的无符号值 xi。对于介于 100 ~ 106 之间的 x 的值,xi 的值在 0 ~ 6 之间,因为 x-100 的负值会绕回成非常大的无符号数。因此,当 xi 大于 6 时,代码用 ja(无符号大于)指令来跳转到默认开关情况的代码。用 jt 来指向跳转表,代码会执行一个跳转,转移到表中表项 xi 处的地址。注意,这种形式的 goto 不是合法的 C 语句。
  • 指令4实现的是到跳转表中某个表项的转移。因为是间接跳转,目标是从存储器中读出的。读的有效地址是由标号 .L10 指定的基地址加上变量 xi(放在寄存器 %eax 中)的伸缩值(伸缩因子值为4,因因为跳转表的每个表项都是 4 个字节)确定的。

在汇编代码中,跳转表是用下面这样的声明表示的,添加了一些注释:

在这里插入图片描述
这些声明表明,在叫做 “.rodata” (表示“只读数据”,“Read-Only Data”)的目标代码文件的段中,应该有一组 7 个 “长” 字(4 个字节),每个字的值都是与指定的汇编代码标号(例如,.L4)相关的指令地址。标号 .L10 标志着这段分配的起始。与这个标号相对应的地址会作为间接跳转(指令4)的基地址。

switch_eg_impl 中(上文的(b) 代码),从标号 loc_A 开始,一直到 loc_Dloc_def 的代码块,实现了 switch 语句的五个不同的分支。可以观察到,当 x 超出 100 ~ 106 范围时(初始范围检查),或者当它等于101 或 105 时(根据跳转表),都会执行标号为 loc_def 的代码块。注意标号为 loc_B 的代码块是如何落入标号为 loc_C 的代码块的。

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

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

相关文章

Maven高级5-私服

1. 简介 一台独立的服务器&#xff0c;用于解决团队内部的资源共享与资源同步问题&#xff08;模拟了中央服务器&#xff09;&#xff1b; https://help.sonatype.com/repomanager3/download 命令行启动服务器 在nexus.exe目录&#xff08;bin目录&#xff09;下启动cmd&#…

【认知提升思维篇】之 反刍思维--恶性思考的根源

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;普本…

代码随想录算法训练营第五十一天 | 力扣 309.最佳买卖股票时机含冷冻期, 714.买卖股票的最佳时机含手续费

309.最佳买卖股票时机含冷冻期 题目 309. 最佳买卖股票时机含冷冻期 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成更多的交易&#xff08;多次买卖一…

Vulnhub | 实战靶场渗透测试 - PRIME: 1

0x00 免责声明 本文仅限于学习讨论与技术知识的分享&#xff0c;不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;本文作者不为此承担任何责任&#xff0c;一旦造成后果请自行承担…

“本草”大模型开源,ChatGPT时代,连AI私人医生都出现了?

大家好,我是千与千寻,也可以叫我千寻,今天给大家分享的ChatGPT新应用项目,是ChatGPT模型在医学领域的应用,什么,医学领域? 是的,没错,是医学领域的ChatGPT应用,我们都知道ChatGPT是OpenAI开源的一个智能对话式引擎,今天给大家分享的项目叫“本草”。 “本草”模型是…

IIC协议与OLED

1.认识OLED 1、OLED概述&#xff1a; OLED&#xff08;Organic Light-Emitting Diode&#xff0c;有机发光二极管&#xff09;是一种显示技术&#xff0c;利用有机材料的发光特性来产生光。OLED显示器由一系列有机材料层组成&#xff0c;当电流通过时&#xff0c;这些材料会发…

【性能调优】真实体验 “系统调用是重开销”

实践背景是开发云原生背景下的指纹识别插件&#xff0c;主要针对的是镜像、容器等云时代的软件资产。 信息安全语境下的 指纹识别 指的是定位软件的特征&#xff0c;如名称、版本号、开源许可证等&#xff0c;就像指纹是人的独特生物凭证&#xff0c;这些特征是软件的独特电子凭…

六一儿童节 全网最全的微服务+Outh2套餐,你确定不来试一试?(入门到精通,附源码)满足你的味蕾需要(二)

咱们废话不多说&#xff0c;直接开干&#xff01;&#xff01;&#xff01; 目录 一、项目目录 二、Token 三、授权服务器oauth 1.pom 2.application 3.OauthApp启动类 4.DiyUserDetails 5.MyUserDetailService 6.KeyPairController 7.TokenConfig 8.WebSecurityCo…

LNMP架构

LNMP架构 一、LNMP架构原理二、LNMP部署1、安装 Nginx 服务1.安装依赖包2.创建运行用户3.编译安装4.优化路径5.添加 Nginx 系统服务 2、安装 MySQL 服务1.安装Mysql环境依赖包2.创建运行用户3.编译安装4.修改mysql 配置文件5.更改mysql安装目录和配置文件的属主属组6.设置路径环…

电子模块|压力传感器模块HX711---硬件介绍

电子模块|压力传感器模块HX711---硬件介绍与C51&&STM32驱动 实物照片模块简介模块特点 硬件模拟输入供电电源时钟选择串口通讯复位和断电HX711相关部分的 PCB 设计 实物照片 模块简介 HX711是一款专为高精度称重传感器而设计的24位A/D转换器芯片。与同类型其它芯片相比…

后端接口调式工具

后端接口调式工具 目录概述需求&#xff1a; 设计思路实现思路分析1.Postman2.Swagger 文档测试工具3.Sniff 文档测试工具4.APIpost 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardne…

【SCADA】启动KingSCADA运行系统,提示加载报警库服务失败?

大家好&#xff0c;我是雷工&#xff01; 今天启动KingSCADA时&#xff0c;发现无法运行&#xff0c;提示“加载 报警库服务 失败”&#xff0c;现将问题排查及解决问题的过程记录如下。 一、问题描述&#xff1a; 1、提示如下图&#xff1a; 2、信息窗口提示&#xff1a; …

性能测试从零开始落地实施全过程指南之性能测试计划怎么写?

目录 前言 一、测试背景 二、测试目的 三、测试范围 四、术语约定 五、环境说明 六、需求分析 七、测试策略 八、准备工作 九、组织架构 十、风险分析 十一、交付清单 十二、阶段进度 前言 最近有些同学找我咨询关于性能测试计划相关的问题&#xff0c;原因是他们…

《C++ list的模拟实现》

本文主要介绍list容器的模拟实现 文章目录 1、迭代器正向迭代器类反向迭代器类 2、push_back尾插函数3、 push_front头插函数4、 insert插入函数5、erase删除函数6、pop_front函数7、pop_back函数8、 构造函数9、 拷贝构造函数10、 list赋值重载函数11、clear12、 析构函数程序…

AI注册流程

1、首先需要有一个OpenAI账号&#xff0c;如果有方法的&#xff0c;就可以自己先注册一下。如果没有方法的&#xff0c;还有一个付费版本的可以备选&#xff0c;亲测可用。 2、注册建议使用谷歌账号关联登录&#xff0c;最方便。微软账号太慢了&#xff0c;也可以使用。注册使用…

SAP-MM库存进销存报表

1、总览&#xff1a; 事务代码MB5B是查询选择期间之内的收发存报表&#xff1b; 其中&#xff0c;收、发为汇总选择期间的收、发信息&#xff0c;存为选择期间的期初、期末库存数据&#xff1b;我们也可以用该报表查询历史上某一天的库存&#xff0c;但注意有一些限制条件。 …

【Selenium】提高测试爬虫效率:Selenium与多线程的完美结合

前言 使用Selenium 创建多个浏览器&#xff0c;这在自动化操作中非常常见。 而在Python中&#xff0c;使用 Selenium threading 或 Selenium ThreadPoolExecutor 都是很好的实现方法。 应用场景&#xff1a; 创建多个浏览器用于测试或者数据采集&#xff1b;使用Selenium…

Region Proposal Network (RPN) 架构详解

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 如果您正在阅读这篇文章[1]&#xff0c;那么我假设您一定听说过用于目标检测的 RCNN 系列&#xff0c;如果是的话&#xff0c;那么您一定遇到过 RPN&#xff0c;即区域提议网络。如果您不了解 RCNN 系列&#xff0c;那么我…

Github copilot的详细介绍,竞品比对分析,效率使用方法总结。

Copilot介绍&#xff0c;与竞品对比 Copilot是GitHub和OpenAI合作开发的一款人工智能代码助手&#xff0c;它可以根据用户输入的注释和代码片段&#xff0c;自动生成高质量的代码。Copilot使用了OpenAI的GPT模型&#xff0c;可以学习和理解大量的代码库和文档&#xff0c;从而…

javascript基础十三:说说 typeof 与 instanceof 区别?

一、typeof typeof 操作符返回一个字符串&#xff0c;表示未经计算的操作数的类型 举个粟子&#xff1a; typeof 1 number typeof 2 string typeof undefined undefined typeof false boolean typeof Symbol() symbol typeof null object typeof [] object typeof {} object…