文章目录

  • 2.9 人机交互
  • 2.10 LEGv8中的宽立即数和地址的寻址
    • 2.10.1 宽立即数
    • 2.10.2 分支中的寻址
    • 2.10.3 LEGv8寻址模式总结
    • 2.10.4 机器语言解码
  • 2.11 并行与指令:同步
  • 2.12 翻译并启动程序
    • 2.12.1 编译器
    • 2.12.2 汇编器
    • 2.12.3 链接器
    • 2.12.4 加载器
    • 2.12.5 动态链接库
    • 2.12.6 启动Java程序
  • 2.13 综合实例:C排序程序
    • 2.13.1 swap过程
      • 1 为swap分配寄存器
      • 2 为swap过程体生成代码
      • 3 完整的swap过程
    • 2.13.2 sort过程
      • 1 为sort分配寄存器
      • 2 为sort过程体生成代码
      • 3 sort 中的过程调用
      • 4 sort中的参数传递
      • 5 在sort中保存寄存器
      • 6 完整的sort过程

2.9 人机交互

计算机发明最初是用于处理数字的,但在进入商业应用时,计算机已经能够处理文字了。今天计算机基本都使用8位的字节来表示字符,并遵行美国信息交换标准代码(即ASCII码)。

注意,在ASCII码中,所有大写字母和对应小写字母的差均为32,这个规律可以用于快速检查和切换大小写。 另外一个有用的ASCII值是0,表示null,C语言常用它来标记字符串的结尾。

通过使用一系列指令可以从一个双字中提取出一个字节,所以load register 和 store register 足以完成字和字节的传输。然而,很多程序中经常要处理文本,因此LEGv8额外提供了字节移动指令。读字节指令LDURB(load byte)从内存中读出一个字节,并将其放在一个寄存器最右边的8位。存字节指令STURB(store byte)把一个寄存器最右边的8位(即一个字节)取出,并写到内存。这样,我们可以通过以下指令序列复制一个字节:

LDURB X9,[X0,#0] // 读取字节
STURB X9,[X1,#0] // 向目的地写 字节

字符通常被组合为字符数目可变的字符串。表示一个字符串有三种方法:(1) 保留字符串的第一个位置用于给出字符串的长度;(Java采用该种方法)(2)附加一个指明字符串长度的变量(如再结构体中)。(3)字符串最后的位置用一个字符来标识其结尾。C语言使用第三种方法,用一个值为零(ASCII码中的null)的字节来结束字符串。因此,在C语言中,字符串“Cal” 由4字节表示,由十进制分别表示为67,97,108,0.

例题
编译一个字符串复制过程,体会如何使用C语言的字符串

过程strcpy利用C语言中以null字节结束字符串的规定,将字符串y赋值给字符串x:

void strcpy( char x[],char y[]){size_t i;i=0;while((x[i]=y[i])!='\0')  //赋值并且判断是否符合条件i+=1;}

请给出编译后的LEGv8汇编代码。

下面是在DS-5中 反汇编得到的结果,值得研究

strcpy0x0000000080001218:    SUB      sp, sp, #0x200x000000008000121C:    STR      x0, [sp, #0x8]0x0000000080001220:    STR      x1, [sp]0x0000000080001224:    STR      xzr, [sp, #0x18]0x0000000080001228:    B        0x800012380x000000008000122C:    LDR      x0, [sp, #0x18]0x0000000080001230:    ADD      x0, x0, #0x10x0000000080001234:    STR      x0, [sp, #0x18]0x0000000080001238:    LDR      x1, [sp]0x000000008000123C:    LDR      x0, [sp, #0x18]0x0000000080001240:    ADD      x1, x1, x00x0000000080001244:    LDR      x2, [sp, #0x8]0x0000000080001248:    LDR      x0, [sp, #0x18]0x000000008000124C:    ADD      x0, x2, x00x0000000080001250:    LDRB     w1, [x1]0x0000000080001254:    STRB     w1, [x0]0x0000000080001258:    LDRB     w0, [x0]0x000000008000125C:    CMP      w0, #0x00x0000000080001260:    B.NE     0x8000122c0x0000000080001264:    NOP0x0000000080001268:    ADD      sp, sp, #0x200x000000008000126C:    RET

答案

下面是基本的LEGv8代码片段。假设数组x和y的基址在X0和X1中,而i在X19中。strcpy调整栈指针,然后将保存寄存器X19保存在栈中。

strcpy:SUBI SP,SP,#8   //调整堆栈指针,为一个元素留出空地STUR X19, [SP,#0]//SAVE X19

Java中的字符和字符串

Java对字符采用Unicode编码,默认情况下,Unicode使用16位来表示一个字符。ASCII使用8位表示一个字符。

LEGv8指令集中的一些指令能够显示地读取和存储16位量,即半字。读半字指令LDURH(load half) 从存储器中读出一个半字,然后将其放到寄存器的最右边16位。与读字节类似,读半字指令LDURH也将半字看作有符号苏,并进行符号扩展以填充寄存器中剩余的48位。存半字指令STRUH(store half)将寄存器最右边的16位写入寄存器。 下面的指令可以复制半字

LDURH X19,[X0,#0]//从源地址读取半字(16bits)
STURH X9,[X1,#0]//向目的写入半字

下面哪种类型的变量存放1000 000 000占用的内存空间最大?
A C语言中的long long int
long long在win32中存在,长度为8个字节;定义为LONG64。
B C语言中的 string
C Java中的string
答案是C

参考:Java String 占用内存大小分析

2.10 LEGv8中的宽立即数和地址的寻址

2.10.1 宽立即数

2.10.2 分支中的寻址

LEGv8跳转指令(无条件分支指令)采用最简单的寻址方式,使用B型LEGv8指令格式,操作码为6位,其余为都是地址段。

B 10000 // go to location  10000(Decimal)

可以汇编成下面的格式(实际中更为复杂):

5 10000ten10000_{ten}10000ten​
6位 26位

其中,跳转指令的操作码值为5,分支地址为10000ten10000_{ten}10000ten​。

和跳转指令不同,条件分支指令除了分支地址之外还可以指定一个操作数。
因此:

CBNZ X19,Exit  //go to Exit if X19≠0

被汇编成下面的指令,其中只有19位用于指定分支地址:

181 Exit 19
8位 19位 5位

对于条件分支指令,这种格式叫作CB型。

如果程序的地址只能放在19位的字段中,这意味着没有程序能大于2192^{19}219,这在今天来说实在太小,因此是一种很不现实的选择。

另一种方法是指定一个寄存器,该寄存器的值用于和分支地址的偏移量相加以得到最终地址,这样分支指令的地址可按下面的方式计算:
程序计数器=寄存器内容+分支地址偏移量程序计数器=寄存器内容+分支地址偏移量程序计数器=寄存器内容+分支地址偏移量

这个求和结果允许程序的大小得到2642^{64}264,并且仍能使用条件分支,从而解决了分支地址大小的问题。但随之而来的问题是,使用哪个寄存器?

答案取决于条件分支是如何使用的。 条件分支在循环和if语句中都可以找到,它们倾向于转向附近的指令。例如,在SPEC基准测试程序中,大概一半的条件分支转移范围都在16条指令以内。因为程序计数器(PC)包含当前指令的地址,所以如果我们使用PC作为计算地址的寄存器,就可转移到距当前指令±218±2^{18}±218个字(1个字=32位)的地方。 几乎所有的循环和if语句都远远小于±218±2^{18}±218个字,因此PC是一个理想的选择。这种分支地址的寻址方式称为PC相对寻址(PC-relative addressing).

PC相对寻址:一种寻址方式,将PC和指令中的常数相加作为地址。

像近期大多数计算机一样,LEGv8对所有条件分支使用PC相对寻址,因为这些指令的跳转目标一般都比较接近分支指令本身。 另一方面,分支和链接(branch-and-link)指令引发的过程则并不一定总是靠近调用者,所以通常使用其他寻址方式。 因此,LEGv8 体系结构通过对分支指令以及分支和链接指令采用B型指令格式,为过程调用提供长地址。

因为LEGv8的所有指令都是4字节长,所以将PC相对寻址的地址设计成字地址(1个字=32位)而不是字节地址,从而可以扩展分支转移的范围。 通过将字段解释成相对字地址而不是相对字节地址,19位的地址字段所指示的转移范围扩大了4倍:当前PC±1MB。同样,分支指令的26位字段 也是字地址,即表示28位字节地址。

这里的1MB是怎么算出来的呢? 19位地址提供2182^{18}218种寻址方式,又因为是字地址,还需要乘以4倍(即222^222)所以 寻址大小为218×22=220=1MB2^{18}\times 2^2=2^{20} =1MB218×22=220=1MB

无条件分支(即跳转指令)也采用PC相对寻址,这意味着转移范围是当前PC值±128MB。

这里的128MB是怎么算出来的呢? 26位地址提供2252^{25}225种寻址方式,又因为是字地址,还需要乘以4倍(即222^222)所以 寻址大小为225×22=227=220×27=128MB2^{25}\times 2^2=2^{27}=2^{20} \times 2^7 =128MB225×22=227=220×27=128MB

2.10.3 LEGv8寻址模式总结

2.10.4 机器语言解码

小测验:

LEGv8中条件分支的地址范围多大(K=1024)?

答案: 分支前后大约1024K(即1MB)的地址范围

LEGv8中条件分支的指令格式为

opcode Address Rt
8位 19位 5位

其中19位用于指定分支地址。

这里的1MB是怎么算出来的呢? 19位地址提供2182^{18}218种寻址方式,又因为是字地址,还需要乘以4倍(即222^222)所以 寻址大小为218×22=220=1MB2^{18}\times 2^2=2^{20} =1MB218×22=220=1MB

LEGv8中跳转和跳转链接指令的地址范围(M=1024K)是多大?

答案:分支前后大约128M的地址范围

opcode addressaddressaddress
6位 26位

这里的128MB是怎么算出来的呢? 26位地址提供2252^{25}225种寻址方式,又因为是字地址,还需要乘以4倍(即222^222)所以 寻址大小为225×22=227=220×27=128MB2^{25}\times 2^2=2^{27}=2^{20} \times 2^7 =128MB225×22=227=220×27=128MB

另外关于MIPS指令跳转的地址范围可参考下面这篇文章:MIPS中分支和跳转的地址范围

2.11 并行与指令:同步

2.12 翻译并启动程序

本节描述了将一个存储在外存(磁盘或闪存)某文件中的C程序转换为计算机上可执行程序的4个步骤。

C语言的翻译层次。用高级语言编写的程序首先被编译为汇编语言程序,然后被汇编为机器语言组成的目标模块。链接器将多个模块和库例程组合在一起解析所有的引用。加载器将机器代码加载到内存的适当位置供处理器执行。为了加速翻译过程,某些步骤被跳过或和其他步骤结合在一起。


图片来自:《计算机组成与设计(ARM版)》英文版

为了识别文件类型,UNIX使用文件的后缀,x.c代表C源文件,x.s表示汇编文件,x.o代表目标文件,x.a代表静态链接库,x.so代表动态链接库,a.out默认情况下表示可执行文件。

MS-DOS使用后缀.C表示C源文件,.ASM代表汇编文件,.OBJ代表目标文件,.LIB代表静态链接库,.DLL代表动态链接库,.EXE代表可执行文件。

2.12.1 编译器

2.12.2 汇编器

2.12.3 链接器

2.12.4 加载器

2.12.5 动态链接库

2.12.6 启动Java程序

Java程序并不会被编译成目标计算机的汇编语言,而是首先被编译成易于解释的指令序列—Java字节码(Java bytecode)指令集。 该指令集被设计得与Java语言接近,因此编译步骤相对简单。事实上,并不需要进行任何优化。就像C语言编译器那样,Java编译器检查数据类型并且为每种数据类型生成正确的操作。Java程序最终将转化成这些字节码的二进制形式。


图片来自:《计算机组成与设计(ARM版)》英文版
一种称为Java虚拟机(Java Virtual Machine,JVM)的软件解释器能够执行Java字节码。解释器是一个用来模拟一种指令集体系结构的程序。例如,这门书提供下载的DS-5,ARMv8模拟器就是一种解释器。由于翻译非常简单,地址可以由编译器填写或在运行时被JVM发现,因此不需要单独的汇编步骤。

解释的优势是可移植性。

解释的不足是性能较差。 和传统方式编译的C程序相比,相差10倍的性能。

为了既能够保持可移植性又提高执行速度,Java发展的下一阶段是实现能够在程序执行同时进行翻译的编译器。这种即时编译器(Just In Time complier,JIT)通过记录运行的程序来找到“热点”(“热点”指程序中运行特别频繁的代码块),然后将它们编译成Java虚拟机所运行于的宿主机上的本地指令。编译过的部分被保存起来供下次程序运行时使用,从而使以后每次运行变得更快。

小测验: 和 翻译器相比,对Java开发者来说,解释器的哪些优点是最重要的?
回答: 机器独立性。即与机器无关。

2.13 综合实例:C排序程序

2.13.1 swap过程

一个将内存中两个不同位置的内容进行交换的C过程。

void swap(long long int v[],size_t k){//数组v,位置k
//交换 v[k]和v[k+1]long long int tmp;tmp=v[k];v[k]=v[k+1];v[k+1]=tmp;
}

我们按照以下步骤把该过程从C语言手动翻译成汇编语言:

  1. 为程序变量分配寄存器
  2. 为过程体生成汇编代码
  3. 保存过程调用间的寄存器

1 为swap分配寄存器

在LEGv8中,使用寄存器X0到X7进行参数传递。由于swap只有两个参数v和k,因此可以被分配给寄存器X0和X1.仅剩的另一个变量是tmp,由于swap是一个叶过程,我们将其分配给寄存器X9.

寄存器分配如下表:

数组v k tmp
X0 X1 X9

2 为swap过程体生成代码

LEGv8存储地址按字节编址,因此双字由8个字节组成。因此索引k需先乘上8,再与地址相加。忘记连续的双字之间的地址相差8而不是1,是用汇编语言编程时常见的错误。

因此,第一步通过左移3位来将k乘以8以获得v[k]的的地址:

LSL X10,X1,#3 // X10=K*8
ADD X10,X10,X0 // X10=V+K*8

接下来根据X10取出v[k]的值,并将X10加8得到v[k+1]:(使用寄存器X9和X11)

LDUR X9,[X10,#0]  //X9=V[K]
LDUR X11,[X10,#8] //X10=V[K+1]

最后将X9和X11存储到需要交换数据的地址中

STUR X11,[X10,#0]// 交换
STUR X9,[X10,#8]

至此,我们已经为该过程分配了寄存器并获得了实现交换操作的代码。保存swap中使用的保存寄存器的代码并没有包括在其中。但由于我们并不使用叶过程(这里swap是一个叶过程)中的保留寄存器,因此没有需要保留的东西。

3 完整的swap过程

现在得到完整的例程,包括过程标签以及返回的跳转指令。

swap:LSL X10,X1,#3 //k*8ADD X10,X10,X0//得到v[k]LDUR X9,[X10,#0] //取出到寄存器LDUR X11,[X10,#8]STUR X9,[X10,#8]//交换,存储到存储器STUR X11,[X10,#0]BR LR  //过程返回

2.13.2 sort过程

该例中,我们将编写一个调用swap过程的例程,使用冒泡排序算法(这种排序算法虽然不是最快的,但确是最简单的)对数组中的整数进行排序。
一个对数组v中元素进行排序的C过程(冒泡排序):

void sort(long long int v[],size_t int n){//数组v和数组长度nsize_t i ,j;for(i=0;i<n;i++){for(j=i-1;j>=0&&v[j]>v[j+1];j--)//从小到大排序swap(v,j);//交换v[j]和v[j+1]}}

1 为sort分配寄存器

寄存器X0和X1分配给过程sort的两个参数v和n,寄存器X19和X20分配给变量i和j。
寄存器分配如下表:

数组v n i j
X0 X1 X19 X20

2 为sort过程体生成代码

过程体包含两个嵌套的for循环和一个带参数的swap过程。下面由外向内来展开代码。

第一步翻译第一个for循环

for(i=0;i<n;i++){

C语言中的for语句有三个部分:初始化、循环条件判断和迭代递增。将i初始化为0只需要一条指令,故for循环的第一部分为:

MOV X19,XZR //i=0

(注意:MOV是为了方便汇编程序员编程而由汇编器提供的伪指令)。将i递增同样也只需要一条指令实现,因此for语句的最后部分为:

ADDI X19,X19,#1 //i+=1

当i<n非真时需要退出循环,换句话说,也就是i≥n时退出循环。循环条件判断需要两条指令:

for1tst: CMP X19,X1 //比较X19和X1(也就是i和n)B.GE exit1 //如果(i≥n)则跳转 到exit1

循环最后仅仅跳回循环条件判断的地方:

 B for1tst  // branch to test of outer loop
exit1:

第一个for循环的框架代码如下:

     MOV X19,XZR
for1tst:CMP X19,X1  //循环条件判断B.GE exit1...(body of first  for loop)...ADDI X19,X19,#1//i+=1B for1tst //回到for1tst
exit1:

第二个for循环的C语句如下:

for(j=i-1;j>=0&&v[j]>v[j+1];j--)//从小到大排序

这个循环的初始化部分仍然只需要一条指令:

SUBI X20,X10,#1 //j=i-1

循环末尾j的递减也只需一条指令:

SUBI X20,X20,#1//J-=1;

循环条件测试由两部分组成,任何一个条件为假就退出循环。因此,如果第一个条件测试为假(j<0),循环就要退出:

for2tst:CMP X20,XZRB.LT exit2  //if (j<0)  go to exit2

这条跳转指令将跳过第二个条件测试。如果没有跳过,则j≥0.

下面来看第二个条件,当v[j]≤v[j+1]时为假。
获取v[j]

LSL X10,X20,#3
ADD X11,X0,X10// X11存放v[j]

现在取出v[j] (放在寄存器X12中):

LDUR X12,[X11,#0]

因为第二个元素恰好是顺序下一个双字,因此将寄存器X11中的地址值加8就可以取出v[j+1] (存放在X13中):

LDUR X13,[X11,#8]

测试v[j]≤v[j+1],以判断是否跳出循环:

CMP X12,X13
B.LE exit2  //小于等于则跳转

循环末尾跳转到内存循环测试处:

 B for2tst

将这些代码片组合起来就可以得到第二个for循环的框架:

     SUBI X20,X19,#1 //J=I-1
for2tst:CMP X20,XZR  // J AND  0 进行比较B.LT exit2LSL X10,X20,#3ADD X11,X0,X10// X11存放v[j]LDUR X12,[X11,#0]  LDUR X13,[X11,#8]CMP X12,X13B.LE exit2  //小于等于则跳转...(body of second for loop)...SUBI X20,X20,#1//J-=1;B for2tstexit2:

3 sort 中的过程调用

下一步处理第二个for循坏体:

swap(v,j);

调用swap足够简单(一条BL指令即可实现):

BL swap

4 sort中的参数传递

当我们想传递参数时问题出现了,因为sort过程需要使用寄存器X0和X1中的值,而swap过程需要将其参数放入相同的寄存器中。一种解决办法是在过程执行的早期就将sort的参数复制到其他寄存器中,让出X0和X1寄存器供swap过程使用。(这种复制要比使用栈进行保存和恢复快得多。)在过程中,首先将寄存器X0和X1的值用如下方法复制到X21和X22中:

MOV X21,X0 // COPY PARAMETER X0 INTO X21
MOVE X22,X1 //COPY PARAMETER X1 INTO X22

然后用下面两条指令将参数传递给swap:

MOV X0,X21 // swap的第一个参数v
MOV X1,X20//swap的第二个参数j

swap(v,j);

5 在sort中保存寄存器

剩下的代码保存和恢复寄存器的值。显然,我们必须将返回地址保存在寄存器LR中,因为sort是一个过程并且本身也被调用。sort过程还使用了由被调用者保存的寄存器X19,X20,X21和X22,这些寄存器的值也必须被保存。因此,sort过程开头的代码如下:

SUBI SP,SP,#40  // MAKE ROOM ON STACK FOR 5 REGS
STUR LR,[SP,#32]  //SAVE LR ON STACK
STUR X22,[SP,#24] // SAVE X22
STUR X21,[SP,#16] //SAVE X21
STUR X20,[SP,#8] //SAVE X20
STUR X19,[SP,#0] //SAVE X19

过程末尾只需要简单地反向执行这些指令,最后加入一条BR指令以实现返回。

6 完整的sort过程

注意for循环中对寄存器X0和X1的引用被替换成对寄存器X21和X22的引用。

//保存寄存器的值
sort:   SUBI SP,SP,#40  // MAKE ROOM ON STACK FOR 5 REGSSTUR LR,[SP,#32]  //SAVE LR ON STACKSTUR X22,[SP,#24] // SAVE X22STUR X21,[SP,#16] //SAVE X21STUR X20,[SP,#8] //SAVE X20STUR X19,[SP,#0] //SAVE X19
//过程体//传送参数MOV X21,X0 // COPY PARAMETER X0 INTO X21MOVE X22,X1 //COPY PARAMETER X1 INTO X22//外部循环SUBI X20,X19,#1 //J=I-1
for2tst:CMP X20,XZR  // J AND  0 进行比较B.LT exit2LSL X10,X20,#3ADD X11,X0,X10// X11存放v[j]LDUR X12,[X11,#0]  LDUR X13,[X11,#8]CMP X12,X13B.LE exit2  //小于等于则跳转//传递参数和调用MOV X0,X21 // swap的第一个参数vMOV X1,X20//swap的第二个参数jBL swap//内部循环SUBI X20,X20,#1//J-=1;B for2tst//外部循环
exit2:  ADDI X19,X19,#1B for1tst//恢复寄存器的值
exit1:  STUR X19,[SP,#0] //SAVE X19STUR X20,[SP,#8] //SAVE X20STUR X21,[SP,#16] //SAVE X21STUR X22,[SP,#24] // SAVE X22STUR LR,[SP,#32]  //SAVE LR ON STACKSUBI SP,SP,#40  // MAKE ROOM ON STACK FOR 5 REGS//过程返回BR LR  //RETURN TO CALLING ROUTINE

《计算机组成与设计(ARM版)》读书笔记-第二章指令2相关推荐

  1. 《Spring实战》第四版读书笔记 第二章 装配Bean

    2019独角兽企业重金招聘Python工程师标准>>> 在Spring中,对象无需自己查找或创建与其所关联的其他对象.相反,容器负责把需要相互协作的对象引用赋予各个对象. 创建应用对 ...

  2. 《计算传播学导论》读书笔记——第二章文本分析简介

    <计算传播学导论>读书笔记--第二章文本分析简介 第一节 文本分析研究现状 常用文本挖掘技术 第二节 文本分析与传播学研究 (一)为什么文本挖掘技术逐渐受到传播学者的关注 (二)不同文本分 ...

  3. 计算机网络(第五版 作者:AndrewS.Tanenbaum David J.Wetherall 清华大学出版社)读书笔记----第二章的学习

    计算机网络第二章--物理层读书笔记 1.物理层是网络的技术设置,物理层的材质和带宽决定了最大的传输速率. 2.传输介质的分类:引导性(有线介质)和非引导性(无线介质). (1)有线介质:磁介质.双绞线 ...

  4. PHP核心技术与最佳实践 读书笔记 第二章 面向对象的设计原则

    2019独角兽企业重金招聘Python工程师标准>>> 第二章 面向对象的设计原则 2.1 面向对象设计的五大原则 单一职责原则 接口隔离原则 开放-封闭原则 替换原则 依赖倒置原则 ...

  5. C++Primer读书笔记----第二章变量和基本类型

    前段时间学习iphone开发,非常力不从心,很多C/C++的类库代码看不明白.大学的时候都学过C/C++,但是基本上忘光了,现在做C#开发,经常写出来一些性能低下的代码,究其原因就是原理掌握不扎实,乱 ...

  6. C++ Primer Plus读书笔记第二章

    自学了一段时间的C++打算还是要系统的整理一下一些知识点,让学习思路更清晰,不然老是学一点忘一点,这个读书笔记用来记录这段时间对C++ Primer Plus一书中知识点的记录,尽量会写的详细一点.直 ...

  7. 《软件测试经验与教训》读书笔记---第二章

    <软件测试经验与教训>读书笔记--目录 第一章 测试员的角色 第二章 按测试员的方式思考 第三章 测试手段 第四章 程序错误分析 第五章 测试自动化 第六章 测试文档 第七章 与程序员交互 ...

  8. In-memory Computing with SAP HANA读书笔记 - 第二章:SAP HANA overview

    本文为In-memory Computing with SAP HANA on Lenovo X6 Systems第二章SAP HANA overview的读书笔记. 本章最重要的部分是SAP HAN ...

  9. 《Process Analytics 过程分析》读书笔记 第二章 业务过程范例

    第二章 业务过程范例 毫无疑问,在过程管理中的研究和开发为技术革命铺平了道路. 更重要的是,过程管理技术在今天也同样重要,就像它在两三年前成立时一样. 然而,主要转移的是主要重点. 例如,在1990年 ...

最新文章

  1. SAP WM 针对采购订单收货时候不能自动获取物料主数据里的Special Movement Indicator?
  2. 图像“颜色选择”怎么用?
  3. 疫情退票引爆的潘多拉盒子,境外旅游商家濒临倒闭
  4. php long2ip,php 中IPV6 ip2long的问题解决办法
  5. servlet 配置 使用_配置HTTPS以与Servlet一起使用
  6. useReducer使用和原理
  7. 用树莓派3B+实现智能语音识别
  8. 在线二进制转文本字符工具
  9. 蓝宝石vega56刷64bios及降压超频全过程
  10. XMAN选拔赛官方Writeup
  11. android 夜间模式功能,Android 夜间模式的三种实现
  12. mysql常用日期的写法
  13. 深入React v16新特性(二)
  14. 独立版微信动态二维码活码管理系统免授权版
  15. JS判断输入的字符串是否是数字(正则表达式)
  16. 多易教育KAFKA实战(2)-java生产者客户端API示例代码
  17. 勤于奋国外LEAD联盟赚钱注意事项
  18. 卷积神经网络(CNN)相关知识以及数学推导
  19. java类编来那个初始化顺序_java类的初始化顺序
  20. 供应链信息系统建设思路

热门文章

  1. mysql某个表被行锁了_MySQL中的锁(表锁、行锁)
  2. 手机bootstrap搜索框_你知道手机可以对摄像头进行图像调节吗
  3. 用神经网络分类响尾蛇和牛蛙
  4. 比较双曲正切tanh与sigmoid激活函数的性能差异
  5. jdbc配置文件连接mysql_java jdbc使用配置文件连接数据库:
  6. 【UAV】从单个螺旋桨到四旋翼无人机运动学分析
  7. 【控制】《鲁棒控制-线性矩阵不等式处理方法》-俞立老师-第7章-保性能控制
  8. Python 实现图片质量比较之PSNR和SSIM
  9. STM32 电机教程 16 - PMSM电机磁场定向控制原理
  10. Scan Chain的原理与实现(实践) - Compression Flow