linux c内联汇编popl,ShiYanLou/汇编语言.md at master · Ewenwan/ShiYanLou · GitHub
汇编语言简述
内联汇编
G++中的内联汇编分为基本形式的内联汇编与扩展形式的内联汇编;毫无疑问,扩展形式的内联汇编更加复杂,也更加强大
__asm__与asm
两者是一样的,只不过ANSI C标准将asm作为关键字用于其他用途;所以为了与ANSIC兼容,还是使用__asm__;
__volatile__于volatile
告诉编译器,此处禁止优化,与__asm__一同使用,表示不优化内联汇编段
基本形式的内联汇编
可以使用宏定义,来更加方便得使用内联汇编,如
#define _mBeginASM __asm__ __volatile__ (
#define _mEndASM );
基本形式的内联汇编语法如:
_mBeginASM
"汇编代码"
_mEndASM
G++会将"汇编代码"部分逐字插入到为程序生成的汇编代码中,所以应该在每一条指令后面手动添加'\n\t';
扩展形式的内联汇编
语法形式:
_mBeginASM
"汇编代码模板"
:输出参数列表
:输入参数列表
:改动的寄存器列表
_mEndASM
总的作用机理就是:
g++ 首先根据'输入参数列表'部分将输入参数复制到指定的寄存器(使用mov,fld..等指令);
替换一下汇编代码模板中的占位符,然后将该部分逐字插入到为程序生成的汇编代码中
根据'输出参数列表'部分将指定寄存器中的值mov到C++变量中
感觉就像一个函数调用,'汇编代码模板'就是函数体,'输入参数列表'部分指定了函数的参数,'输出参数列表'部分指定了函数的返回值
计算机系统结构
cpu内部:
1. PC Program Counter
指令指针寄存器
指向下一条指令的地址
EIP(X86-32)或者
RIP(X86-64)
2. 寄存器与寄存器堆
Registers
在处理器CPU内部以名字来访问的快速存储单元
3. 条件状态码
Condition Codes
用于存储最近执行指令的结果状态信息
用于条件指令的判断执行
内存单元Memory:
以字节编码的连续存储空间
存储程序代码、数据、运行栈stack 以及操作系统数据
汇编语言数据格式
c 语言 数据类型 汇编代码后缀 大小(字节为单位)
char 字节 b byte 1
short 字 w word 2
int 双字 l 4
long int 双字 l 4
long long int 无 无 4
char* 双字 l 4
float 单精度 s 4
double 双精度 l 8
long double 扩展精度 t 10/12
第一条汇编指令实例
c 语言代码:
int t = x+y // 两个整数(32位)
汇编代码:
addl 8(%ebp) %eax//l表示双字 8是位偏移量
操作数:
x: 寄存器 Register eax
y: 内存 Memory M[ebp+8] ebp是栈基址寄存器
t: 寄存器 Register eax
结果t保存在寄存器eax中
类似于表达:
x += y
或者:
int eax;
int* ebp;
eax += ebp[2];//这里按字节
数据传送指令
movel 源地 目的地
将一个双字从源地移动到目的地
允许的操作数类型有:
立即数Imm:常整数
如: $0x400, $-533
可以用1,2或4个字节来表示
寄存器 Reg:
8个通用寄存器之一
%eax
%ebx
%ecx
%edx
%esi
%edi
%esp 栈顶
%ebp 栈底
存储器Mem:四个连续的字节
汇编 类似C语言
立即数--->寄存器 movl $0x41, %eax temp = 0x41;
立即数--->内存 movl $-43, (%eax) *p = -43;
寄存器--->寄存器 movl %eax, %edx temp2 = temp;
寄存器--->内存 movl %eax, (%edx) *p = temp;
内存 --->寄存器 movl (%eax), %edx temp = *p;
不允许内存到内存
简单得寻址模式
1. 间接寻址 (R) Mem[Reg[R]]
寄存器R指定得内存地址
movl (%ecx), %eax
2. 基址+便宜量寻址 D(R) Mem[Reg[R] + D]
寄存器R指定内存的起始地址
常数D给出偏移地址
movl 8(%ebp), %ecx
寻址模式使用示例
交换两个数
C语言
void swap(int*xp,int*yp){
intt0 =*xp;
intt1 =*yp;
*xp = t1;
*yp = t0;
}
汇编语言分析
寄存器 变量:
%ecxyp
%edxxp
%eaxt1
%ebxt0
对应汇编:
movl12(%ebp),%ecx#ecx= yp 是地址 放到ecx寄存器中
movl8(%ebp),%edx#edx= xp 是地址 放到edx寄存器中
movl (%ecx),%eax#eax=*yp t1 值 取寄存器中地址指向的内存地址中的内容放入 寄存器eax中
movl (%edx),%ebx#ebx=*xp t0 值 取寄存器中地址指向的内存地址中的内容放入 寄存器ebx中
movl %eax,(%edx) #*xp =eax交换内容放入原来内存指向得地址中
movl %ebx,(%ecx) #*yp =ebx
ebp是函数栈 基地址
ebp+8的位置 存储 指针xp 指向内存的一个地址
ebp+12的位置 存储 指针yp 指向内存的一个地址
变址寻址
常见形式:
D(Rb,Ri,S) Mem[Reg[Rb] + S*Reg[Ri] + D]
D: 常量(地址偏移量)
Rb: 基址寄存器:8个通用寄存器之一
Ri: 索引寄存器: %esp不作为索引寄存器
一般%ebp也不做这个用途
S: 比例因子, 1,2,4,8
其他变形:
D(Rb,Ri) Mem[Reg[Rb] + Reg[Ri] + D]
(Rb,Ri) Mem[ Reg[Rb] + Reg[Ri] ]
(Rb,Ri) Mem[Reg[Rb] + S*Reg[Ri]]
地址计算指令 leal lea +l
leal src,dest
src 是地址计算表达式子
计算出来得地址赋给 dest
使用实例:
地址计算,无需访问内存 auto*p = &x[i];
进行x+k*y这一类型得整数计算,k =1,2,4,8
整数计算指令:
addl src,dest # dest = dest+src #加法
subl src,dest # dest = dest-src #减法
imull src,dest # dest = dest*src #乘法
sall src,dest # dest = dest << src #左移位 等价于shll
sarl src,dest # dest = dest >> src #算术右移位 补 被移动数的最高位
shrl src,dest # dest = dest >> src #逻辑右移位 左边单纯补 0
xorl src,dest # dest = dest ^ src #按位异或
andl src,dest # dest = dest & src #按位与
orl src,dest # dest = dest | src #按位或
incl dest # dest = dest+1#++自增1
decl dest # dest = dest-1#--自减1
negl dest # dest =-dest # 取非
notl dest # dest = ~ dest # 取反
将leal指令用于计算
c语言:
inttemp(intx,inty){
intt1 = x+y;
intt2 = z+t1;
intt3 = x+4;
intt4 = y*48;
intt5 = t3+t4;
intret= t2*t5
returnret;
}
汇编代码:
movl8(%ebp),%eax#eax= x
movl12(%ebp),%edx#edx= y
leal (%eax,%edx),%ecx# t1 =ecx= x+y
addl16(%ebp),%ecx# t2 =ecx= z+t1
leal (%edx,%edx,2),%edx#edx=3*y
sall $4,%edx# t4 =edx=2^4*3*y =16*3*y =48*y
leal4(%eax,%edx),%eax# t5 =eax=4+x+t4
imul%ecx,%eax#ret=eax= t2*t5
逻辑运算示例
C语言:
intlogical_(intx,inty){
intt1 = x^y;
intt2 = t1>>17;
intmask = (1<<13)-7;
intret= t2 & mask;
returnret;
}
汇编语言:
movl8(%ebp),%eax#eax= x
xorl12(%ebp),%eax# t1 =eax= x^y
sarl $17,%eax# t2 =eax= t1 >>12
andl $8185,%eax#ret=eax= t2 &81852^13-7=8185这里编译器会算出来
x86-32 与 x86-64主要类型数据宽度得区别
x86-32 x86-64
long int 4 8
char* 4 8 内存地址编号长度
使用 条件码(标志寄存器) 来进行控制
addl src,dest t =a+b
CF Carry Flag 进位标志 可用于无符号整数运算的溢出
SF Sign Flag 符号位标志 结果<0 的话,被设置为1
ZF Zero Flag 0结果标志 结果为0的话,被设置为1
OF Overflow Flag 溢出标志 if a>0 & b>0 & t<0 被设置为1
比较指令 compare 被比较数 比较数 compare b,a a-b(但是不会改变a的值)
compare b,a a-b
if a == b , ZF =1
if a < b , SF=1
if (a>0 && b<0 && (a-b)<0)||(a<0 && b>0 && (a-b)>0) OF=1
测试指令 test 做与操作
testl s2,s1
testq s2,s1
计算 s1 & s2并设置相应的条件码,不改变目的操作数
testl b,a 计算a&b
if a&b ==0, ZF =1
if a&b < 0, SF =1
CF=0,OF=0
setX指令 获取条件码状态
sete 相等 结果为0 ZF =1时
setne 不相等 ZF =0
sets 结果
setns 结果>=0 SF=0
有符号数:
setg 大于
setge 大于等于
setl 小于
setle 小于等于
无符号数
setb 小与 below
seta 大于 ablove
读取条件码实例
c语言:
intgt(intx,inty){
return x>y;
}
汇编语言:
movl12(%ebp),%eax#eax= y
cmpl %eax,8(%ebp) # compare x:y x-y
setg%al#al寄存器是eax的低八位得名字al= x>y
movzbl #al,%eax#8位扩展到16位
跳转指令 Jx
jmp 无条件跳转
je 相等
jne 不相等
js 结果
jns 结果>=0
有符号
jg 大于
jge 大于等于
jl 小于
jle 小与等与
无符号
ja 大于
jb 小于
跳转指令 Jx 实例
c语言:
intabs_(intx,inty){
intrest;
if(x>y){
rest= x-y;
}
else{
rest= y-x;
}
return rest;
}
汇编语言:
movl8(%ebp),%edx#edx= x
movl12(%ebp),%eax#eax= y
cmpl %eax,%edx# x-y
jle.L7 # x <=y 跳转到.L7
subl %eax,%edx# x>y,计算rest = x-y
movl %edx,%eax# 结果放在 寄存器eax指向的地址
.L8:
leave
ret
.L7:
subl %edx,%eax# x <=y 跳转到.L7 计算 y-x
jmpL8
循环的汇编语言表示
for循环:
for(Init初始条件; 判断测试条件Test; 更新量Update)
循环体Body;
转换到 While-do结构:
Init初始条件;
while(判断测试条件Test){
循环体Body;
更新量Update;
}
再转换成go-to结构:
Init初始条件;
goto middle; # 无条件跳转 jmp
loop:
循环体Body;
更新量Update;
middle:
if(判断测试条件Test)
gotoloop;
switch case的汇编语言格式
c语言版本:
long switch_eg(long x,long y,long z){
long w =1;
switch(x){
case1:
w = y*z;
break;
case2:
w = y/z;
break;
case3:
w+=z;
break;
case5:
case6:
w-= z;
break;
defult: // 这里 case0和 case4和其他情况
w =2;
}
return w;
}
汇编语言版本:
pushl %ebp# 保存 寄存器老的ebp的值
movl %esp,%ebp# 函数堆栈 栈顶指针 %esp存放在%ebp
pushl %ebx# 保存 寄存器ebx的值
movl8(%ebp),%ebx# 函数堆栈距离栈顶 偏移8位处存放x的值
movl12(%ebp),%eax# y
movl16(%ebp),%ecx# z
cmpl $6,%ebx# x-6
jbe.L11 # X <=6才进入正常的 case
.L8: # default 部分
movl $2,%eax# w =2
jmp.L12 # break
.L11:
jmp*.L7(,%ebx,4) %访问.L7段表 对应的 case block
.section.rodata
.L7:
.long .L8 # case0情况 进入 default
.long .L3 # case1情况
.long .L4 # case2情况
.long .L9 # case3情况
.long .L8 # case4情况 进入 default
.long .L6 # case5情况 case6
.long .L6 # case6情况 进入 default
.L12: # break
popl %ebx# 后进先出
popl %ebp# 先进后出
ret# 函数段返回
.L3: # case1
imull %ecx,%eax# w = w*z
jmp.L12 # break
.L4: # case2
movl %eax,%edx
sarl $31,%edx
idivl %ecx# w =y/z
addl %ecx,%eax# case3w+= z
jmp.L12 # break
.L9: # case3
movl %1,%eax# w =1
addl %ecx,%eax# w+=z
jmp.L12 # break
.L6: # case6case5
movl $1,%eax# w =1
subl %ecx,%eax# w-= Z
jmp.L12 # break
x86-32的程序栈
栈---水桶结构的一块内存区域---->只有一个出口(栈底%ebp (桶口)高地址(计算机地址逆向生长))
先进后出后进先出 FILO
寄存器 %esp 存储栈顶地址(下部)
寄存器 %ebp 存储栈底地址(上部)
注意 %esp %ebp 始终指向当前正在运行的活动的函数过程栈
存储的内容
局部变量
返回地址
临时空间
栈帧的分配与释放
1. 进入过程 先分配栈帧空间
set-up code
2. 过程返回时 释放栈帧空间
finish code
pushl 压栈 把大象装进水桶的操作
水面栈顶%esp会上什(步进单位为4个内存地址块)
pushl src # 从src取得操作数
# %esp= %esp-4栈顶上升 水面上什
popl 出栈操作 把大象从水桶中取出来
水面栈顶%esp会下降
popl Dest # 读取栈顶数据(%esp) 放入 目标位置Dest
# %esp= %esp+4栈顶下降 水面下降
过程调用 call label 过程返回指令 ret
过程调用指令:
calllabel #将返回地址(call指令的下一条指令地址(%eip)压入栈),跳转至label继续执行
过程返回指令
ret#跳转至栈顶的返回地址
函数调用时 寄存器的使用惯例
8个寄存器:
两个特殊寄存器:
调用者和被调用者可能都需要保存
始终指向当前活动的堆栈上两端
%ebp 栈底指针
%esp 栈顶指针
三个由调用者保存:
%eax 常用于保存过程的返回值的
%ecx
%edx
三个由被调用者保存:
%ebx
%esi
%edi
递归调用实例
c语言版本:
int rfact(int x){
int rval;
// 递归出口
if(x<=1) return 1;
// 递归调用
rval = rfact(x-1);
return rval * x;
}
汇编语言版本:
调用者栈情况 前 %ebp
前 %ebx
变量x 父过程调用的参数
返回地址 adr
# 进入过程 先分配栈帧空间
被调用者(函数rfact) 老的 %ebp第一步需要保存%ebppushl %ebp指向 前%ebp
第二步 movl %esp,%ebp
老的 %ebx第三步 pushl %ebx
(用来存储函数内的变量值,之前的值需要先保存)
主体代码:
movl8(%ebp),%ebx#ebx= x
cmpl $1,%ebx# compare x :1,计算 x-1
jle.L78 # x <=1直接跳转到递归出口 .L78
leal-1(%ebx),%eax#eax= x-1被调用者这里需要用到 %eax作为过程的返回值的
pushl %eax#pushx-1入栈保存 这里用作为调用者需要保存 %eax
callrfact # rval = rfact(x-1),函数返回值保存在 %eax中
imull %ebx,%eax# rval*x
jmp.L79 # 跳转到 done
.L78:
movl $1,%eax# returneax=1
# 返回代码 过程返回时 释放栈帧空间
.L79:
movl-4(%ebp),%ebx# 复原老的 %ebx的值
movl %ebp,%esp# 栈顶指针 %esp也指向%ebp指向的位置
popl %ebp# 恢复老的 %ebp
ret
x86-64的通用寄存器
x86-32 有 8个32位的寄存器
8个寄存器:
两个特殊寄存器:
调用者和被调用者可能都需要保存
始终指向当前活动的堆栈上两端
%ebp 栈底指针
%esp 栈顶指针
三个由调用者保存:
%eax 常用于保存过程的返回值的 累加器 计算操作数和存放结果数据
%ecx 计数寄存器
%edx 数据寄存器
三个由被调用者保存:
%ebx 基础寄存器 指向DS数据段的数据指针
%esi 源索引
%edi 目的索引
x86-64 有 16个64位的寄存器:
与 x86-32兼容的8个64位的寄存器
%rax 低16位也叫 %eax eax 低16位称为ax,ax的高8位称为ah,低8位称为al
%rbx %ebx ebx 低16位称为bx,bx的高8位称为bh,低8位称为bl
%rcx %ecx ecx 低16位称为cx,cx的高8位称为ch,低8位称为cl
%rdx %edx edx 低16位称为dx,dx的高8位称为dh,低8位称为dl
%rsi %esi esi 低16位称为si
%rdi %edi edi 低16位称为di
%rsp %esp esp 低16位称为sp
%rbp %ebp ebp 低16位称为bp
新增加的8个64位的寄存器:
%r8 低16位也叫 %r8d
%r9 %r9d
%r10 %r10d
%r11 %r11d
%r12 %r12d
%r13 %r13d
%r14 %r14d
%r15 %r15d
x86-32 6个16位的段寄存器,定义内存中的段
CS 代码段 存储指令和执行的地方
DS, ES, FS, GS 数据段
SS 堆栈段 当前程序存储堆栈的地方
数组的表示 访问 操作
访问:
c语言:
typedefintarray_i_5[5];// 定义一个长度为5的整数数组类型 array_i_5
array_i_5 cmu = {1,2,5,3,4};// 定义一个数组cmu 函数五个整形变量
cum[index];//可以获取相应索引位置处的 元素
汇编语言:
# %edx= cmu 数组首地址
# %eax= index 索引
movl (%edx,%eax,4),%eax# cmu[index]4为一个整形类型所占据的直接数量
数组循环遍历修改
c语言:
void temp(array_i_5 z){
inti;
for (i=0; i<5; i++)
z[i]++;// 数组各元素自增1
}
汇编语言:
movl $0,%eax# 循环变量 %eax= i
.L4:
addl $1,(%edx,%eax,4) # z[i]++
addl $1,%eax# i++
cmpl $5,%eax# compare i:5
jne.L4 # i <5循环
多维数组的表示
intarr_i_i[row_index][col_index];
一个int类型占据4个字节地址
则 arr_i_i[y][x]的实际地址为 arr_i_i+y*row_index*col_index+4*x
例如一个 arrii[4][5]的二维数组,每一行占有4*5=20个字节
那么 arrii[i][j]的地址为 arrii+i*20+4*j
arrii+4*(i+4*i)+4*j
相关汇编代码:
先计算列地址
leal0(,%ecx,4),%edx#4*j
leal (%eax,%eax,4),%eax#5*i
movl arrii(%edx,%eax,4),%eax# arrii+4*j+4*(i+4*i)
hello world程序
//helloworld.c
#include
#include
intmain(){
printf("Hello World\n");
exit(0);
return0;//这里就不会编译了
}
// gcc 编译
gcc-S-O2 helloworld.c
-S 生成汇编代码(与系统类型相同) helloworld.s
-m3264位系统生成32位汇编
-On-O22级编译优化
// asm
movl $LC0 (%esp) #设置过程调用参数 字符串首地址 放在栈顶对应的地址
call_puts
movl $0,(%esp) #设置过程调用参数
call_exit
标记符号
.text #代码段
.p2align4,,15#对齐方式 按16=2^4字节对齐,填充,,0,最大填充15个字节
.section.rdata"dr"#只读数据段
LC0:# 字符串的起始地址
.ascii"Hello World\12\0"
linux 汇编命令
编译:
//gcc -S -O2 helloworld.c
as -o my-object-file.o helloworld.s
# -gstabs 产生带调试信息的object文件
# 64位环境下添加命令行 --32 生成 32位目标文件
链接成执行程序
ld -o my-exe-file my-object-file.o
# 64位环境下添加命令行 -m elf_i386 生成 32位目标文件
helloworld.s
.data #数据段
msg: # 字符 首地址
.ascii"Hello World\n"
len = .-msg # 字符长度 . 表示当前地址
.text # 代码段
.globl _start # 汇编程序入口地址,如同C语言的main函数
_start:
movl $len,%edx# 字符串长度 字节数量
movl $msg,%ecx# 字符串存储位置的起始地址
movl $1,%ebx# 系统输出(write 系统调用1)
movl $4,%eax#
int $0x80# 中断 来指向系统调用
#eax存放的是系统调用的功能号
#ebx为参数 为1时 是 write 显示器输出
#edx字符串长度 字节数量
#ecx字符串存储位置的起始地址
movl $0,%ebx# 系统调用(程序退出0)
movl $4,eax
int $0x80# 中断 来指向系统调用
linux c内联汇编popl,ShiYanLou/汇编语言.md at master · Ewenwan/ShiYanLou · GitHub相关推荐
- linux c内联汇编popl,使用内联汇编注意问题
c++代码,,采用g++编译器.如果内联函数内的汇编使用了ebx寄存器,则如果按照-fPIC方式编译,则有下面错误提示 错误:重新加载'asm'时在类'BREG'中找不到寄存器 错误:'asm'操作数 ...
- linux gcc 内联汇编入门
目录 2. 概览(Overview of the whole thing.) 3.GCC汇编语法(GCC Assembler Syntax.) 3.1. 源-目标顺序(Source-Destinati ...
- linux arm gcc 内联汇编参考手册
关于本文档 GNU C 编译器为 ARM 精简指令系统处理器提供了在 C 代码中内嵌汇编的功能.这种非常酷的特性提供了一些 C 代码没有的功能,比如手工优化软件关键代码.使用相关的处理器指令. 本文假 ...
- 在Visual C++ 中使用内联汇编
一. 优点 使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤.在 Visual C++ 中,内联汇编是内置的编译器,因此不需要配置诸如 MASM 一类的独立汇编工 ...
- 用VC写Assembly代码(7)--在Visual C++中使用内联汇编
在Visual C++中使用内联汇编 一. 优点 使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤.在 Visual C++ 中,内联汇编是内置的编译器,因此不需 ...
- 在 Visual C++ 中使用内联汇编
在 Visual C++ 中使用内联汇编 一. 优点 使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤.在 Visual C++ 中,内联汇编是内置的编译器,因此 ...
- linux 64位module内联汇编,@yuanbor: Linux内联汇编总结
1.基本格式 __asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify); 注意:内联汇编 ...
- Linux C中内联汇编的语法格式及使用方法
在阅读Linux内核源码或对代码做性能优化时,经常会有在C语言中嵌入一段汇编代码的需求,这种嵌入汇编在CS术语上叫做inline assembly.本文的笔记试图说明Inline Assembly的基 ...
- 须使用visual c 内联汇编语言开发,在VisualC 中使用内联汇编
在VisualC 中使用内联汇编 2008-04-09 04:08:57来源:互联网 阅读 () 一.内联汇编的优缺点 因为在Visual C 中使用内联汇编不需要额外的编译器和联接器,且可以处理Vi ...
最新文章
- 我的android studio开发环境搭建历程(一部辛酸史)
- 开发web前端_移动前端开发和web前端开发的区别?
- AVA:Netflix的剧照个性化甄选平台
- 线程与进程的区别_Java线程和PC进程的区别
- 设单链表中存放n个字符,试设计一个算法,使用栈推断该字符串是否中心对称...
- ElasticFusion: Dense SLAM without A pose Graph
- 关于vue el-button 动态获取个数并附不同的颜色
- 实用!Excel在线网页版表格Luckysheet源码
- 计算机无法显示外接硬盘,移动硬盘不显示怎么办解决教程
- 汉字编码及区位码查询算法
- 类似微信的即时通讯服务器,除了微信,还有这些常用即时通讯APP
- 机器学习——逻辑回归算法代码实现
- 基于实物的智能化仓储管理-InStock
- 联想E14笔记本 不插鼠标出现鼠标自动漂移乱串
- 论如何熟悉HTML与css的方法-孰能生巧第一步:美食网编写(详细代码)
- echarts scatter3D 图标陷进地图
- ibmt60升级linux,【原创】老兵不死,T60最强升级记
- linux彻底卸载nginx,linux彻底删除nginx
- 数据分析师是否是青春饭,对年龄有限制吗?
- getsockopt和setsockopt
热门文章
- 玩游戏也能借钱?这家公司推出游戏贷,催用户还钱被要求“叫爸爸”
- javascript location.reload()
- 解决maven构建时警告:The artifact xxx has been relocated to xxx
- 大学物理学第四版课后习题答案(赵近芳)上册
- 在Windows实现类似MacOS的时间壁纸,编写脚本自定义自己喜欢的壁纸
- RPCA 稳健主成分分析/鲁棒主成分分析
- 企业年均增速、企业年均增长率、三年利润平均增长率、三年销售平均增长率
- smartscreen筛选器阻止了这个不安全的下载
- 阿里云服务器性能测试方法(一条命令搞定)
- 使基于CentOS8的Docker镜像支持中文