程序的机器级表示

程序的机器级表示

有关CSAPP第三章一些我关注到的重点的记录

操作指令

.c->.exe的流程

image-20231127125503391

1.选项 -E : 预编译过程,处理宏定义和include,并作语法检查

gcc -E hello.c -o hello.i              #将hello.c预处理输出为hello.i文件

2.选项 -S : 编译过程,生成通用的汇编代码

gcc -S hello.c                         #生成汇编代码hello.s

生成的汇编文件以“.”开头的行都是指导汇编器和链接器工作的伪指令

3.选项 -c : 汇编过程,生成ELF格式的可重定位目标文件,目标文件(机器代码),用文本编辑器打开是乱码

gcc -c hello.c                         #生成目标代码hello.o(中间文件),不能执行,在Makefile中应用广泛

4.选项 -L : 链接过程,将.o文件与所需库文件链接合并成ELF格式的可执行目标文件,分静态链接和动态链接

gcc hello.o -L dir(如./lib)            #指定库搜索路径,有多个则从前往后搜索

5.选项 -l : 链接过程,指定链接库,库命名规则是libxxx.a,指定库名时使用的格式是-lxxx

gcc hello.c -o hello -lm              #链接数学库
ld -o hello hello.o -lxxx             #链接xxx库

6.选项 -o : 将源文件预处理、编译、汇编并链接形成可执行目标文件,-o选项指定可执行文件的文件名,加载到内存中即可执行

gcc hello.c -o hello                  #生成可执行文件hello

7.部分选项 :
选项 -Wall : 编译时打开警告信息开关
选项 -D : 在文件中定义宏INFO,编译时加上-D INFO使其生效
选项 -O : 后指定数字,使用编译优化级别1~3优化程序
选项 -g : 产生调试信息

8.选项 -static : 使用静态链接库,将使用的静态库对象嵌入至可执行映像文件中,加载时无需进一步的链接

gcc -c -Wall x1.c x2.c       #生成目标文件
ar -cru libxxx.a x1.o x2.o   #创建静态库
#定义静态库的应用接口xxx.h,里面显式引用上面的源文件函数和对象
gcc -O2 -c main.c            #测试用例调用静态库的函数
gcc -static -o p main.o ./libxxx.a  #链接静态库和目标文件生成可执行文件p

9.选项 -share : 使用共享库,在运行时动态加载目标程序所需要的信息
选项 -fPIC : 指示编译器生成与地址无关的目标文件(position-independent code)

gcc -shared -fPIC -o libxxx.so x1.c x2.c  #生成共享库libvector.so
gcc -o p1 main.c ./libvector.so           #共享库中的目标对象并未嵌入可执行文件中,执行时完成链接过程

.c->.exe

linux> gcc -Og -o p -g p.c
  • -Og优化等级比较符合原始C代码整体结构,方便学习(为了更高的性能可以使用-O1或-O2甚至更高的编译优化选项)
  • -o转化成可执行文件
  • -g生成调试信息
  • p为转化成可执行文件的文件名
  • p.c为源文件名

.c->.s 编译生成汇编文件

linux> gcc -Og -S p.c

.c->.o 汇编生成目标文件

linux> gcc -Og -c p.c

.o/.exe->.s 反汇编

linux> objdump -d p.o

C语言嵌套汇编语言

C编译器在把程序中表达的计算转换到机器代码中表现很出色,但仍然有一些及其特性是C语言访问不到的。例如x86-64处理器执行算术或逻辑运算时,修改奇偶标志位寄存器PF的值时,用汇编语言的效率远高于C语言,故如果能在C语言中嵌套C语言,会提供大大的方便。

第一种方法:源代码中插入汇编代码

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

int main(void)
{
	/* basic command demo */
	__asm__("movl %eax, %ecx");

	/* set b = 10 */
	int a = 10, b = 0;
	__asm__("movl %1, %%eax;"
	     "movl %%eax, %0;"
	     :"=r" (b)	/* output */
	     :"r" (a)	/* input */
	     :"%eax"	/* clobbered register */
	     );

	printf("%s: b = %d\n", __func__, b);
	return 0;
}

第二种方法:写好汇编文件和C文件,用汇编器和链接器把它们合并起来

保存寄存器

假设现在有两个函数funcA和funcB,函数A称为调用者,函数B称为被调用者,由于调用了函数B,寄存器rbx在函数B中被修改了,而逻辑上rbx寄存器的内容在调用函数B的前后应该保持一致,解决这个问题有两个策略,调用者保存和被调用者保存。

func_A:
	...
	movq $123, %rbx
	call func_B
	add %rbx, %rax
	...
	ret
func_B:
	...
	addq $456, %rbx
	...
	rer

调用者保存

func_A:
	...
	movq $123, %rbx
	保存rbx
	call func_B
	恢复rbx
	add %rbx, %rax
	...
	ret
func_B:
	...
	addq $456, %rbx
	...
	rer

被调用者保存

func_A:
	...
	movq $123, %rbx
	call func_B
	add %rbx, %rax
	...
	ret
func_B:
	...
	保存rbx
	addq $456, %rbx
	恢复rbx
	...
	rer

具体使用哪种策略取决于寄存器被定义为那种类型,下图是寄存器类型

image-20231119162344823

c语言基本类型对应汇编后缀表示

image-20231119162527234

访问信息

各存储部件的性价比

image-20231119162939434

通用寄存器
寄存器用途
%eax操作数运算
%ebx指向DS段中数据的指针
%ecx字符串操作和循环计数器
%edx输入输出指针
%esi指向DS段中数据的指针或字符串操作中字符串的复制源
%edi指向ES段中数据的指针或字符串操作中字符串的复制地
%esp栈指针(SS段)
%ebp指向SS段上数据的指针
段寄存器
寄存器用途
CS代码段
DS数据段
SS堆栈段
ES数据段
FS数据段
GS数据段

C类型长度

C声明Intel数据类型汇编代码后缀大小(字节)
char字节b1
shortw2
int双字l4
long四字q8
char*四字q8
float单精度s4
double双精度l8

指令

指令包含操作码和操作数。

	操作码		操作数
	movq	 (%rdi), %rax
	addq	 $8,	%rsx
	subq	 %rdi,	%rax
	xorq	 %rsi,	%rdi
	ret

操作码决定CPU执行操作的类型

指令可以有一个、多个或没有操作数

操作数分为3类,分别为立即数、寄存器以及内存引用

数据寻址模式

image-20231127125528494

这里的比例因子s会根据数据类型取

数据传送命令

image-20231127125732051

以上命令中没有movzlq,是因为一个结论:当复制和生成字节以寄存器为目标时,对于生成4字节的指令,会把高位4个字节置为0,所以用movl就能代替命令movzlq,例如

movl %eax,%edx

实际上除了将低32位数据由eax传递给rdx的低32位之外,还把高32位设置为0

这里注意到练习题3.3的一题,找以下代码的错误

movl %eax,%rdx

在这里错误是源操作数和目标操作数类型不匹配,虽然eax传值后会扩展为64位,但在写代码时依然需要保持操作数类型的统一

movq指令的限制:

当movq指令的源操作数是立即数时,只能是32位的立即数,此时会对该立即数进行符号扩展到64位,再将得到的64位立即数传送到目的位置。

那么当源操作数是64位立即数时就引入了一个新的指令movabsq,此时就能将64位立即数作为源操作数,但目的操作数只能是寄存器

cltq指令

cltq = movslq %eax,%rax

算术和逻辑操作

操作指令

image-20231127125536013

具体操作如下图,之所以z被分为两步操作,是因为比例因子只能取1、2、4、8这四个数中的一个

image-20231120193534676

移位操作

移位量可以是一个立即数,或者放在单字节寄存器%cl中。

移位操作对w位长的数据值进行操作,移位量是由**%cl寄存器**的低m位决定的,这里2m=w,高位被忽略。所以,例如寄存器%cl的十六进制值为0xFF时,指令salb会移7位,salw会移15位,sall会移31位,而salq会移63位。

SAR算术右移,高位补符号位;SHR逻辑右移,高位补0;

以下操作使用移位操作而不使用乘法操作的原因是因为乘法指令执行需要更长时间,因此编译器在生成汇编指令时,会优先考虑更高效的方式。

image-20231120194527867

特殊的算术操作

image-20231127125544066

控制

条件码

CPU除了提供上面的几个整数寄存器外,还维护着一组单个比特位的条件码,描述最近的算术或逻辑操作特性,用于执行条件分支指令。

  • CF: 进位标志,表示最近的操作使最高位产生了进位。用于检查无符号操作数的溢出,如下图image-20231120195141469

  • ZF: 零标志,表示最近的操作得出的结果为0,如下图

    image-20231120195223613

  • SF: 符号标志,表示最近的操作得出的结果为负数

  • OF: 溢出标志,表示最近的操作使补码溢出-正溢出或负溢出

条件码寄存器的值是由ALU执行算术逻辑运算指令改变的

有几种设置条件码的情形

image-20231120195517929

INC(加一)和DEC(减一)指令会设置OF(溢出)标志和ZF(零)标志,但不会改变CF(进位)标志。

因为指令系统设计人员考虑该指令主要用于对指针(即地址)进行增加,不存在进位问题,所以没有设计让INC影响进位标志CF。
INC,DEC指令不影响CF标志位,这个是Intel规定的!其原因是硬件设计造成的,总之,对软件人员来制说不重要!
INC,DEC指令不影响CF标志位,这表明执行INC/DEC指令之后,CF不能反映进位情况。

INC 0000000011111111

0000000011111111+1当然要进位,但不设置CF为1。
我们的问题就在于,将进位与CF等同
CF被称为进位标志位,在多数情况下,它确实反映进位情况,但不是绝对的,INC/DEC就是其中两例
INC/DEC指令不影响CF标志位,这句话就是明明白白地告诉你,此时,CF与进位无关

A. 比较和测试指令:它们只设置条件码而不改变任何其他寄存器

cmp S2,S1 通过S1-S2的结果,比较两者的大小
test S2,S1 通过S1&S2的结果(按位与),比如testl %eax,%eax用来检查%eax是正数,负数还是0或者其中一个操作数是掩码,用来指示哪些位应该被测试

B. 根据条件码的组合,使用set指令,不同后缀名表示不同条件

set指令的目的操作数是8个单字节寄存器或者存储一个字节的存储器位置,把该字节位置设置成0或1。它的基本思路是执行比较或测试指令,根据set指令的类型决定计算结果t=a-b:操作数的大小,是有符号的还是无符号的,程序值的数据类型。如图所示为set指令的常见情形

image-20231127125554540

跳转指令

image-20231127125601201

关于跳转指令如何编码

image-20231127125810240

可以看到第2行中跳转指令目标指明位0x8,第5行中跳转指令跳转目标是0x5,这里有一个规则,在指令的字节编码中,我们可以看到第二个字节中编码位0x3,再将其加上下一条指令的地址,即0x5,就可以得到跳转目标地址0x8,同样第5行0xf8(即十进制-8),这个数加上0xd,即为地址0x5

条件分支

用条件控制来实现条件分支
实际上,C语言中有一种语句叫做goto,一般不推荐使用,但是它的控制和汇编代码的条件转移十分相似。

例如我们有这样一段正常的代码,实际上就是得到两数之差的绝对值:

long absdiff(long x, long y)
{
long result;
if (x > y)
result = x-y;
else
result = y-x;
return result;
}

然后我们使用goto语句改写一下:

long absdiff_j(long x, long y)
{
long result;
int ntest = x <= y;
if (ntest) goto Else;
result = x-y;
goto Done;
Else:
result = y-x;
Done:
return result;
}

从控制流的角度来看,这两个代码基本上是一样的。

用条件传送来实现条件分支
条件传送,和set指令有些相似,也就是根据条件码部分来判断是否要进行数据传送,使用的是cmov(conditional move),比如当相等的时候进行条件传送,也就是cmove。

现代处理器会使用一种特殊的技术,叫做流水线(pipeline),它的名字就是取自工厂流水线,在CPU中,也就是说当你执行一条指令的时候,下一条指令的一部分会被执行,下下一条指令的一部分也会被执行,这样就提高了并行的程序。

但是条件转移会破坏流水线的运作,于是我们会把两个条件的结果都计算一遍,然后再根据跳转选择其中的一条。这里也就用到了cmov。

比如还是之前的程序,我们汇编变成如下这个样子,也就是把x-y和y-x都计算了,然后再根据条件,选择其中一个结果返回:

absdiff:
movq %rdi, %rax # x
subq %rsi, %rax # result = x-y
movq %rsi, %rdx
subq %rdi, %rdx # eval = y-x
cmpq %rsi, %rdi # x:y
cmovle %rdx, %rax # if <=, result = eval
ret
但是,使用cmov也会有一些负面影响:

只有当计算较为简单时,才用cmov进行优化,如果两条分支都较为复杂,那么使用cmov反而不好
对于某个分支而言,计算它可能没有什么用,只是浪费时间。
两个分支可能会存在关联性,比如val = x > 0 ? x*=7 : x+=3;,如果两个都进行计算就会出现错误。

指令同义名传送条件描述
cmove S,RcmovzZF相等/零
cmovne S,Rcmovnz~ZF不相等/非零
cmovs S,RSF负数
cmovns S,R~SF非负数
cmovg S,Rcmovnle~(SF^OF) & ~ZF大于(有符号>)
cmovge S,Rcmovnl~(SF^OF)大于或等于(有符号>=)
cmovl S,RcmovngeSF^OF小于(有符号<)
cmovle S,Rcmovng(SF^OF) | ZF小于或等于(有符号<=)
cmova S,Rcmovnbe~CF & ~ZF超过(无符号>)
cmovae S,Rcmovnb~CF超过或相等(无符号>=)
cmovb S,RcmovnaeCF低于(无符号<)
cmovbe S,RcmovnaCF | ZF低于或相等(无符号<=)

练习题3.20

在这里发现一个规则,当负数做被除数时,需要将该数先加上2k-1,k为要右移的位数。这是为了保证,正数向下舍入,负数向上舍入

image-20231127125626439

循环

一、do-while

如果用C的goto来实现,则如下面的代码:

loop:
  Body
  if (Test)
    goto loop

实际上就是先循环体,然后进行测试,如果测试成功,那么跳回到loop再继续循环。

举个例子,比如我们有这样一个C程序的goto版本:

long pcount_goto (unsigned long x) {
  long result = 0;
 loop:
  result += x & 0x1;
  x >>= 1;
  if(x) goto loop;
  return result;
}

那么会发现,汇编的版本也类似:


 movl    $0, %eax		#  result = 0
.L2:			# loop:
   movq    %rdi, %rdx	
   andl    $1, %edx		#  t = x & 0x1
   addq    %rdx, %rax	#  result += t
   shrq    %rdi		#  x >>= 1
   jne     .L2		#  if (x) goto loop
   rep; ret

二、while

while和do-while的区别就在于do-while第一次不进行测试,所以总会执行一遍循环体,而while在开始就测试,如果不满足就跳出,不执行。

while的实现有2种方式,第一种方式就是先跳到了do-while的中间,然后进行测试。

C的goto版本如下:

goto test;
loop:
  Body
test:
  if (Test)
    goto loop;
done:

我们还是用pcount这个程序,那么while就是下面这种实现:

long pcount_goto_jtm(unsigned long x) {
  long result = 0;
  goto test;
 loop:
  result += x & 0x1;
  x >>= 1;
 test:
  if(x) goto loop;
  return result;
}

第二种实现方式比较传统,就是一开始进行判断,如果不满足直接goto跳出,满足那么进入到和do-while相同的语句块中。

 if (!Test)
    goto done;
loop:
  Body
  if (Test)
    goto loop;
done:

pcount的第二种while实现如下:

long pcount_goto_dw(unsigned long x) {
  long result = 0;
  if (!x) goto done;
 loop:
  result += x & 0x1;
  x >>= 1;
  if(x) goto loop;
 done:
  return result;
}

三、for

for循环实际上包含了4个部分,例如一个C语言的for循环for(int i = 0;i < 5;i++){body},包括了初始化(int i = 0),测试(i < 5),更新(i++)和循环体。

如果用while循环来表示for循环,那么就是先进行初始化,然后是while循环,在while循环体的最后加上更新操作。

Init;
while (Test ) {
    Body
    Update;
}

还是之前的例子,我们使用for循环(用while实现for)实现:

long pcount_for_while(unsigned long x)
{
  size_t i;
  long result = 0;
  i = 0;
  while (i < WSIZE)
  {
    unsigned bit = 
      (x >> i) & 0x1;
    result += bit;
    i++;
  }
  return result;
}

然后我们用goto替代:

long pcount_for_goto_dw(unsigned long x) {
  size_t i;
  long result = 0;
  i = 0;
  if (!(i < WSIZE))
    goto done;
 loop:
  {
    unsigned bit = 
      (x >> i) & 0x1;
    result += bit;
  }
  i++;
  if (i < WSIZE)
    goto loop;
 done:
  return result;
}

如果使用了-O1优化级别,那么第一次的判断很有可能不需要了,编译器会将其舍弃

这里需要提示一下,再跳转指令后若跟随ret会出现一些判断问题,所以我们需要在中间加一个rep;这个什么都不会做,所以也不需要管

过程

栈帧

当函数执行所需要的存储空间超出寄存器能够存放的大小时,就会借助栈上的存储空间,这部分存储空间称为函数的栈帧

image-20231120202419343

如果一个函数的参数数量大于6,超出部分就要使用栈来传递。

image-20231120202519873

两点注意

1.通过栈传递参数时,所有数据大小向8对齐

image-20231120202751702

2.使用寄存器进行参数传递时,寄存器的使用是由特殊顺序规定的

image-20231120202835825

局部变量在栈帧存储不需要对齐,参数才需要对齐

image-20231120203257254

数组

不同类型指针加1,得到结果不同

image-20231120203818045

数组元素的计算

image-20231120204106003

xd表示数组的起始地址,L表示数组类型T的大小,如果T时int类型,L就等于4,T是char类型,L就等于1

例如下图

image-20231120204236228

使用以下汇编代码,将A[i][j]的值复制到寄存器eax中,如下图所示

image-20231120204338191

结构体

结构体在内存中的存储遵循内存对齐,如下图,由于变量j是int类型,占4个字节,它的起始地址必须是4的倍数,所以,在变量c和变量j之间插入了一个3字节的间隙,结构体大小也就变成了12个字节。

image-20231120205126457

如果我们变更顺序,如下图,此时能满足结构体的对齐要求,但无法满足结构体数组的对齐要求,所以如果定义结构体数组,需要在末端加入3个字节的间隔。

image-20231120205242216

**复杂示例:**每个元素的偏移地址都必须是它数据大小的倍数,且为满足每个元素都对齐,最后要在结构体末端填充间隙(根据结构体最大类型的长度),如下图所示。

image-20231120205456633

联合体

联合体中所有字段共享同一存储区域,因此联合体的大小取决于它最大字段的大小,如下图,变量v和数组i的大小都是8个字节,因此该联合体占8个字节的存储空间,两个不同字段的使用是互斥的,那么我们就可以将这两个字段声明为一个联合体。

image-20231120205849564

示例:一个二叉树,包含叶子节点(只包含两个double数据)和内部节点(只包含左右节点指针),其定义如下图,使用结构体定义需要占用32个字节,而使用联合体只用占用16个字节。

image-20231120210147693

但此时有一个问题,就是无法确定节点是哪种节点,解决办法是引入一个枚举类型,如下图所示,type占4个字节(枚举占4个字节),加上最后末尾间隔的4个字节,最终这个结构体占24个字节

image-20231120210512788

**类型转换:**一种类型来存储,另一种类型来访问

image-20231120210926653

栈溢出攻击

解决通过栈溢出攻击系统的三种办法

1.栈随机化

栈的位置在程序每次运行时都发生变化,在Linux系统中,栈随机化已经成为了一种标准行为(ASLR)

2.栈破坏检测

编译器会在产生的汇编代码中加入一种栈保护者的机制来检测缓冲区越界,就是在缓冲区与栈保存的状态值之间存储一个特殊值(金丝雀值,canary),函数返回之前检测金丝雀值是否被修改来判断是否遭受攻击

image-20231121005508305

3.限制可执行代码区域

这三种机制都不需要程序员做额外的操作,都是通过编译器和操作系统实现的,单独每一种机制都能降低用户的等级,组合起来使用会更有效,不幸的是,仍然有方法能对计算机进行攻击。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE】多线程 (1)

目录 1. 认识线程&#xff08;Thread&#xff09; 1) 线程是什么 2) 为啥要有线程 3) 进程和线程的区别 2.第⼀个多线程程序 3.多线程的其他创建方式 方法二:实现 Runnable 接⼝ 方法三:匿名内部类 方法四:实现Runable, 重写run, 匿名内部类 方法五:使用lambda表达式…

爱满荣山·和美岩窝-垃圾分类趣味微课堂

在利州区民政局的支持下&#xff0c;利州社工协会在荣山镇岩窝村开展儿童垃圾分类趣味小课堂。

计算机毕业设计|基于SpringBoot+MyBatis框架的电脑商城的设计与实现(用户上传头像+用户收货管理)

计算机毕业设计|基于SpringBootMyBatis框架的电脑商城的设计与实现&#xff08;用户上传头像&#xff09; 该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先&#xff0c;通过深入分析项目所需数据&#xff0c;包括用户、商品、商品类别、收藏、订单、购物…

Centos7上面部署redis

Centos7上面部署redis 编写这个部署redis&#xff0c;只是为了另一个文章入侵redis做准备&#xff0c;网上还有好多类似的文章&#xff0c;这个单纯的就是部署安装&#xff0c;并简单的测试使用以下 关联其他文章 [1]VMware上面安装部署centos7镜像系统【详细含镜像】 [2]血的教…

计算机组成原理-Cache替换算法

文章目录 总览随机算法&#xff08;RAND&#xff09;先进先出算法&#xff08;FIFO&#xff09;近期最少使用算法&#xff08;LRU&#xff09;最不经常使用算法&#xff08;LFU&#xff09;总结 总览 随机算法&#xff08;RAND&#xff09; 没有选择性地考虑替换哪一块Cache&a…

速通CSAPP(一)计算机系统漫游入门

CSAPP学习 前言 一门经典的计组课程&#xff0c;我却到了大四才学。 anyway&#xff0c;何时都不会晚。 博主参考的教程&#xff1a;本电子书信息 - 深入理解计算机系统&#xff08;CSAPP&#xff09; (gitbook.io)&#xff0c;非常感谢作者的整理。 诚然去看英文版可以学…

谈谈中间件设计的思路

前言 想要设计和真正理解中间件的架构理论和思想。对于开发来说需要具备三个关键的能力 1&#xff1a;基础通用技术的深入理解和运用2&#xff1a;了解和熟悉常见中间件的设计思想&#xff0c;且有自己的感悟,并且能按照自己的理解模仿写一写3&#xff1a;业务的高度理解能力…

解密Spring Cloud微服务调用:如何轻松获取请求目标方的IP和端口

公众号「架构成长指南」&#xff0c;专注于生产实践、云原生、分布式系统、大数据技术分享。 目的 Spring Cloud 线上微服务实例都是2个起步&#xff0c;如果出问题后&#xff0c;在没有ELK等日志分析平台&#xff0c;如何确定调用到了目标服务的那个实例&#xff0c;以此来排…

使用elementPlus去除下拉框蓝色边框

// 下拉框去除蓝色边框 .el-select {--el-select-input-focus-border-color: none !important; }

生成EtherCAT从站XML图片信息方法

0 工具准备 1.PS CS6 2.Hex Editor Neo(文件Hex编辑器) 3.DM3E-556步进电机驱动器 4.TwinCAT(验证XML图片修改效果)1 准备一张需要生成图片信息的图片 根据EtherCAT从站XML图片格式规范,我们需要用到的元素名为ImageData16x14,它要求使用16x14分辨率、深度为16bit的bmp…

【Android Jetpack】Navigation的使用

引入 单个Activity嵌套多个Fragment的UI架构模式&#xff0c;非常非常普遍。但是&#xff0c;对Fragment的管理一直是一件比较麻烦的事情。工程师需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。页面的切换通常还包括对应用程序App bar的管理、Fragme…

Java实现集合和Excel文件相互转换

目录 一、集合转化为Excel文件二、Excel文件转化为集合 一、集合转化为Excel文件 效果如下&#xff0c;是将集合转化为Excel文件&#xff0c;Excel包含合并单元格。 实体类&#xff1a; Data public class ClassGrade {/** 年级 */private String grade;/** 班主任 */privat…

智汇方象惠管家:提升电商系统与广告推广的效率,实现无代码开发的连接

无代码开发的连接优势 在当今快速发展的电商领域&#xff0c;商家们急需一个既能优化电商系统又能提升客服体验的解决方案。智汇方象的惠管家&#xff0c;一个基于云计算的SAAS服务&#xff0c;就是为了满足这一需求而生。这款工具通过无代码开发的方式&#xff0c;使得连接和…

中国北斗:守护萨雷兹湖一方安澜

中国北斗&#xff1a;守护萨雷兹湖一方安澜 在第三届“一带一路”国际合作高峰论坛数字经济高级别论坛上&#xff0c;由中国经济信息社、国家发展改革委高技术司、国家数据局联合编制的《数字“慧”就发展之路》中英文图文集正式发布&#xff0c;展现了中国与共建“一带一路”国…

Codeforces Round 911 (Div. 2) --- D题题解

D. Small GCD Problem - D - Codeforces 题目大意&#xff1a; 给你一个数组&#xff0c;你可以在里面任选三个数ai aj ak&#xff0c;要求i j k 互不相同&#xff0c; 现定义一个函数f(a,b,c)gcd(a,b)&#xff0c;其中a 和 b为a&#xff0c;b&#xff0c;c中较小的两个。求f…

Callable、Future和FutrueTask详解

一、Callable介绍 1.1 Runnable介绍 Runnable是一个接口&#xff0c;里面声明了run方法。但是由于run方法返回值类型为void&#xff0c;所以在执行完成任务后&#xff0c;无法返回任何结果。 FunctionalInterface public interface Runnable {public abstract void run(); }…

SIFT尺度不变特征变换

SIFT(Scale-Invariant Feature Transform)是一种用于图像处理和计算机视觉中的特征提取和匹配的算法。它的主要优点是对图像的尺度、旋转和亮度变化具有较强的鲁棒性。 基本原理: Scale-space peak selection: Potential location for finding features.Keypoint Localizat…

Redis 命令处理过程

我们知道 Redis 是一个基于内存的高性能键值数据库, 它支持多种数据结构, 提供了丰富的命令, 可以用来实现缓存、消息队列、分布式锁等功能。 而在享受 Redis 带来的种种好处时, 是否曾好奇过 Redis 是如何处理我们发往它的命令的呢&#xff1f; 本文将以伪代码的形式简单分析…

【活动回顾】ABeam 德硕| 艾宾信息技术开发(西安)西北高校行——与西北三所高校签订校企合作协议

前言 INTRODUCTION 10月下旬&#xff0c;ABeam旗下艾宾信息技术开发&#xff08;西安&#xff09;校招团队来到宁夏大学、青海大学、兰州大学这三所高校&#xff0c;就校企合作达成多项共识并举行了隆重的签约仪式。ABeam大中华区董事长兼总经理中野洋辅先生也特意留出时间莅临…

【活动回顾】sCrypt在2023伦敦区块链大会上的精彩表现

2023伦敦区块链大会&#xff0c;是本年度最盛大的比特币及区块链行业活动。大会于2023年5月31日至6月2日&#xff0c;在伦敦女王伊丽莎白二世中心举行&#xff0c;旨在展示BSV区块链的真正潜力。 sCrypt Inc 的创始人兼 CEO 刘晓晖&#xff0c; 作为演讲嘉宾出席了会议。他向大…