算术运算通常是指,加减乘除四则运算,而计算机中的四则运算与数学中的有所不同,同样是实现算术运算,高级语言与汇编语言的实现思路完全不同,往往一个简单的减法运算,都要几条指令的配合才能得出计算结果,而为了保证程序的高效率,编译器会对其进行最大限度地优化,这就涉及到汇编代码的逆推,如下笔记则是整理的逆推常用手法。

一般VS系列编译器对代码的优化有两种方案,O1方案则可生成占用空间最小的文件,O2方案则注重执行效率最快,编译器在Release模式下会采用O2方式对代码效率进行优化,所以我们有必要好好研究一下其到底将代码优化成了啥样子,这里为了方便演示我会使用汇编语言模拟编译器生成代码的思路。

加法优化/减法优化

加法常量优化: 当计算结果中出现了两个常数相加的情况,且中间该变量没有被改变过,则就会被优化掉。

#include <stdio.h>
#include <windows.h> int main(int argc,char * argv[])
{
int x = 10;
int y = 20; printf("%d \n", x + y); return 0;
}

如下,我们只看到了一个push 0x1E 编译器发现我们的x + y是两个常数,则为了效率,直接将结果计算出来打印了。

如果我们将代码这样写,那么加法运算将不会被优化掉,因为编译器无法确定,表达式的结果,只能运行后动态计算。

#include <stdio.h>
#include <windows.h> int main(int argc,char * argv[])
{
int x = 10;
int y = 0; for (int y = 0; y < 10; y++)
{
printf("%d \n", x + y);
}
return 0;
}

如下是反汇编后得到的结果,通常情况下加法指令是ADD运算才对,下方代码中并没有出现Add指令,我们的加法计算其实是转化为了lea eax, ds:[esi+0xA],这条指令不仅可以取地址,还可以用来计算加减等运算,lea指令允许用户在一个时钟周期内完成加减法的计算过程,其效率远比add,sub指令高。

这里我直接使用汇编语言来模拟实现编译器对加减法的实现流程,如下代码所示。

.data
x DWORD ?
y DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
; 针对加法的lea指令优化
mov dword ptr ds:[x],5
mov dword ptr ds:[y],3
mov eax,dword ptr ds:[x]
mov ebx,dword ptr ds:[y] mov eax,dword ptr ds:[x]
lea eax,dword ptr ds:[eax + 3] ; eax = eax + 3
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[x]
lea eax,dword ptr ds:[eax + ebx + 2] ; eax = eax + ebx + 2
invoke crt_printf,addr szFmt,eax ; 针对减法的lea指令优化
mov dword ptr ds:[x],6
mov eax,dword ptr ds:[x] lea eax,dword ptr ds:[eax - 2] ; eax = eax - 2
invoke crt_printf,addr szFmt,eax ; 加减法混合优化
mov eax,10
mov ebx,15
lea ebx,dword ptr ds:[eax + ebx - 3 ] ; ebx = eax + ebx - 3
invoke crt_printf,addr szFmt,ebx invoke ExitProcess,0
main ENDP
END main

接着我们来完成一个三数相加的案例,比如说将x+y+100的结果输出,汇编代码如下。

.data
x DWORD ?
y DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],-5
mov dword ptr ds:[y],3 mov eax,dword ptr ds:[x]
mov ebx,dword ptr ds:[y] lea ecx,dword ptr ds:[eax + ebx + 100]
invoke crt_printf,addr szFmt,ecx invoke ExitProcess,0
main ENDP
END main

加减法中的常量传播: 将编译期间可计算出结果的变量转换成常量,这样就减少了变量的使用,如下,由于printf输出的是一个常量,则编译器会对其进行处理,在编译期间计算出变量结果后,直接使用常量10来代替,于是printf("value => %d \n", x);等价于printf("value => %d \n", 10);.

int main(int argc,char * argv[])
{
int x = 10;
int y = 0; printf("value => %d \n", x);
return 0;
}

加减法中的常量折叠: 当计算公式中存在多个常量进行计算时,且编译器可以在编译时动态计算出结果,这样源码中所有的常量计算过程将会被替换掉。

int main(int argc,char * argv[])
{
int x = 10;
int y = 0; int value = 1 + 2 * 3 + 7; printf("value => %d \n", value);
return 0;
}

如上代码中,我们的计算表达式在整个程序运行期间没有发生过变化,则VS编译器在开启O2优化后,会首先计算出int value = 1 + 2 * 3 + 7;表达式的值并将其替换成一个常量值,在打印函数中直接打印计算后的结果,编译器会删除计算的变量,直接替换为常量。

常量折叠+常量传播: 如下代码中由于nVarOne = nVarOne + 1;计算结果是一个常量,则在编译会会被直接替换掉,第二句则满足常量折叠,计算后保留常量值3,最后的加法nVarOne + nVarTwo;虽然在加两个变量,但变量数值未发生变化,同样会被优化为常量.

int main(int argc,char * argv[])
{
int nVarOne = 0;
int nVarTwo = 0; // 变量加常量加法运算
nVarOne = nVarOne + 1; // 0+1 变为 nVarOne = 1
nVarOne = 1 + 2; // 常量折叠 // 两个常量相加的加法运算
nVarOne = nVarOne + nVarTwo;
printf("value = > %d \n", nVarOne);
return 0;
}

如果我们将初始化参数通过命令行获取的话,由于argc在编译期间无法被确定,所以编译器无法在编译时计算出结果,那么程序中的变量将不会被常量替换掉,依然执行加法或减法运算。

int main(int argc,char * argv[])
{
int nVarOne = argc;
int nVarTwo = argc; nVarOne = nVarOne + 1;
nVarOne = nVarOne + nVarTwo;
printf("value = > %d \n", nVarOne);
return 0;
}

减法计算转加法: 减法计算通常使用sub来时间,但计算机只会做加法,如果想要计算减法,只需要通过补码转换将减法转换为加法来计算即可,例如加一个负数同样也相当于减去一个正数。

例如: 5-2 可转换成 5 + (0-2) => 5 + (2(取反)+1) => 5 + 2 补

int main(int argc,char * argv[])
{
int nVarOne = 0;
int nVarTwo = 0; // 防止被优化
scanf("%d", &nVarOne);
scanf("%d", nVarTwo); nVarOne = nVarOne - 10;
nVarOne = nVarOne + 5 - nVarTwo;
printf("varOne = %d \n", nVarOne);
return 0;
}

在某些编译器中,减法运算会通过加法来实现,其实现汇编代码是这个样子的,在实际逆向过程中,加法与减法可以相互转换,只要得到的结果是正确的均可。

.data
x DWORD ?
y DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
; 针对加法的lea指令优化
mov dword ptr ds:[x],100
mov dword ptr ds:[y],3
mov eax,dword ptr ds:[x] ; 例如 100 - 10 可转换为 => 100 + (-10)
mov eax,dword ptr ds:[x]
add eax,0FFFFFFF0h
invoke crt_printf,addr szFmt,eax invoke ExitProcess,0
main ENDP
END main

乘法优化

在汇编语言中,乘法指令通常是使用mul/imul来计算,其分别针对的是无符号与有符号乘法,由于乘法指令在执行时所消耗的时钟周期较长,所以在编译时,编译器会先尝试将其转换为加法,或者使用shr/shl等移位指令来替换,当两者都无法进行优化时,才会使用原始的乘法指令计算。

使用LEA指令替换乘法: 使用lea计算乘法运算时,必须要保证乘数是2的次幂,而且范围必须是2/4/8

.data
x DWORD ?
y DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
; 针对乘法的lea指令优化
mov dword ptr ds:[x],5
mov dword ptr ds:[y],3 mov eax,dword ptr ds:[x]
xor ebx,ebx
lea ebx,dword ptr ds:[eax * 8 + 2] ; ebx = eax * 8 + 2
invoke crt_printf,addr szFmt,ebx invoke ExitProcess,0
main ENDP
END main

如果我们计算的乘法超出了该范围,则需要对乘法进行拆分,拆分时也应遵循2的次幂,拆分后在分开来计算,如下我们需要计算 15 * eax的结果,拆分过程如下。

.data
x DWORD ?
y DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
; 针对乘法的lea指令优化
mov dword ptr ds:[x],5
mov dword ptr ds:[y],3 ; 如果使用lea计算乘法,则乘数必须是2/4/8
mov eax,dword ptr ds:[y] ; eax = 3 => 计算 15 * eax
lea edx,dword ptr ds:[eax * 4 + eax] ; edx = 4eax + eax => 5eax
lea edx,dword ptr ds:[edx * 2 + edx] ; edx = 5eax * 2 + 5eax => 15eax
invoke crt_printf,addr szFmt,edx ; edx = eax * 15 = 45 invoke ExitProcess,0
main ENDP
END main

如果计算乘法时乘数非2的次幂,这种情况下需要减去特定的值,例如当我们计算eax * 7时,由于7非二的次幂,我们无法通过lea指令进行计算,但我们可以计算eax * 8计算出的结果减去一个eax同样可以得到正确的值,例如计算eax * 7 + 10的结果。

.data
x DWORD ?
y DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
; 针对乘法的lea指令优化
mov dword ptr ds:[x],5
mov dword ptr ds:[y],3 ; 如果计算乘法时乘数非2的次幂,则此时需要减
mov eax,dword ptr ds:[y] ; eax = 3 => 计算 eax * 7 + 10
lea edx,dword ptr ds:[eax * 8] ; edx = eax * 8
sub edx,eax ; edx = edx - eax
add edx,10 ; edx = edx + 10
invoke crt_printf,addr szFmt,edx ; edx = eax * 7 + 10 mov eax,dword ptr ds:[y] ; eax = 3 => 计算 eax * 3 - 7
lea edx,dword ptr ds:[eax * 2] ; edx = eax * 2
add edx,eax ; edx = edx + eax
sub edx,7 ; edx = edx - 7
invoke crt_printf,addr szFmt,edx ; edx = eax * 3 - 7 invoke ExitProcess,0
main ENDP
END main

通过使用逻辑与算数,左移,同样可以实现2的次幂的高速乘法运算,如果满足特定条件,编译器生成的代码就会呈现出以下案例中所描述的代码特点。

.data
x DWORD ?
y DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],-5
mov dword ptr ds:[y],3 ; 逻辑左移(无符号乘法)
; 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128
; 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384 mov eax,dword ptr ds:[y]
shl eax,1 ; eax = eax * 2 ^ 1 eax * 2
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[y]
shl eax,2 ; eax = eax * 2 ^ 2 eax * 4
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[y]
shl eax,3 ; eax = eax * 2 ^ 3 eax * 8
invoke crt_printf,addr szFmt,eax ; 算数左移(有符号乘法)
mov eax,dword ptr ds:[x]
sal eax,1 ; eax = eax * 2^1 eax * 2
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[x]
sal eax,2 ; eax = eax * 2^2 eax * 4
invoke crt_printf,addr szFmt,eax invoke ExitProcess,0
main ENDP
END main

乘法优化的基本就这些知识点,除了两个未知变量的相乘无法优化外,其他形式的乘法运算均可以进行优化,如果表达式中存在一个常量值,那编译器则会匹配各种优化策略,最后对不符合优化策略的运算进行调整,如果真的无法优化,则会使用原始乘法指令计算。

除法优化

通常情况下计算除法会使用div/idiv这两条指令,该指令分别用于计算无符号和有符号除法运算,但除法运算所需要耗费的时间非常多,大概需要比乘法运算多消耗10被的CPU时钟,在Debug模式下,除法运算不会被优化,但Release模式下,除法运算指令会被特定的算法经过优化后转化为为乘法,这样就可以提高除法运算的效率。

关于除法运算总结

  1. 如果被除数是一个未知数,那么编译器无法确定数值,则编译器会使用原始的div命令计算,程序的执行效率会变低。
  2. 如果除数是二的次幂,那么可以将其转化为处理速度快的 shr a,n 指令,该指令的执行只需要1个时钟周期,效率最高。
  3. 若进行二的次幂,有符号运算,则只需要使用 sha 进行快速除法运算。

除数为正2的次幂优化(无符号): 如果除数为2的次幂,那么就会使用移位运算替代除法运算,2的次幂还原非常容易,只需要找到移位次数即可得出除以的是多少。

.data
x DWORD ?
y DWORD ?
z DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],5 ; ----------------------------------------------------
; 【除数为2的优化方式】
; 被除数为正数(无需扩展): eax => 5 / 2 = 2
mov eax,dword ptr ds:[x] ; 被除数
sar eax,1 ; 算数右移
invoke crt_printf,addr szFmt,eax ; ----------------------------------------------------
; 【除数为4的优化方式】
; 被除数为正数(无需扩展): eax => 5 / 4 = 1
mov eax,dword ptr ds:[x]
sar eax,2
invoke crt_printf,addr szFmt,eax ; ----------------------------------------------------
; 【除数为8的优化方式】
; 被除数为正数(无需扩展): eax => 5 / 8 = 0
mov eax,dword ptr ds:[x]
sar eax,3
invoke crt_printf,addr szFmt,eax invoke ExitProcess,0
main ENDP
END main

除数为负2的次幂优化(有符号): 当除数为负数时,且为2的次幂的情况下,编译器生成代码时这样的,其还原方式为取得shr eax,xx中的次数,与被除数相除,最后neg取反即可。

.data
x DWORD ?
y DWORD ?
z DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],5
mov dword ptr ds:[y],10
mov dword ptr ds:[z],-10 ; 除数为(有符号)负2的次幂的计算过程
mov eax,dword ptr ds:[y] ; y = 10
cdq ; 符号扩展edx : eax
sub eax,edx ; 减去符号位
sar eax,1 ; eax = 10 / -2
neg eax ; 将正数 eax 翻转为负数 = -5
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[y] ; y = 10
cdq
and edx,3
add eax,edx
sar eax,2 ; eax = 10 / -4
neg eax ; eax = -2
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[z] ; z = -10
cdq
and edx,7
add eax,edx
sar eax,3 ; eax = -10 / -8
neg eax ; eax = 1 (负负得正)
invoke crt_printf,addr szFmt,eax invoke ExitProcess,0
main ENDP
END main

除数为负数的优化(无符号): 如果被除数是一个负数,除数依然是2的次幂,则此时计算后只需要去掉neg取反即可得到正确结果,逆推方式同除数为负2的次幂优化保持一致。

.data
x DWORD ?
y DWORD ?
z DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],-5
mov dword ptr ds:[y],10
mov dword ptr ds:[z],-10 ; 被除数为(有符号)的计算过程
mov eax,dword ptr ds:[z]
cdq
sub eax,edx
sar eax,1 ; eax = -10 / 2
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[x]
cdq
and edx,3
add eax,edx
sar eax,2 ; eax = -5 / 4
invoke crt_printf,addr szFmt,eax mov eax,dword ptr ds:[z]
cdq
and edx,7
add eax,edx
sar eax,3 ; eax = -10 / 8
invoke crt_printf,addr szFmt,eax ; 如果同时为负数的情况
mov eax,dword ptr ds:[z] ; z = -10
cdq
and edx,7
add eax,edx
sar eax,3 ; eax = -10 / -8
neg eax ; eax = 1 (负负得正)
invoke crt_printf,addr szFmt,eax invoke ExitProcess,0
main ENDP
END main

除数为正非2的次幂优化(有符号): 上方的除法运算被除数均为2的次幂,除数的范围也被限定在了2/4/8这样的范围之内,如下是计算非2的次幂的计算方式,如果需要知道除数是多少则可以使用公式2^(32+n) / M计算后得出.

.data
x DWORD ?
y DWORD ?
z DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],5
mov dword ptr ds:[y],10
mov dword ptr ds:[z],-10 ; 除法(有符号)非2的幂转换为乘法 mov ecx,dword ptr ds:[y] ; 被除数 ecx = 10 / 3 = 3
mov eax,055555556h ; eax = M值 1431655766
imul ecx
mov eax,edx ; edx = n 计算: 2^(32+n) / M
shr eax,01fh ; 计算出除数为 2.9999 => 3
add edx,eax
invoke crt_printf,addr szFmt,edx mov ecx,dword ptr ds:[y] ; ecx = 10 / 5 = 2
mov eax,066666667h ; 此处的M模值是编译器计算后得到的
imul ecx
sar edx,1 ; 想要知道除数是多少,只需要
mov eax,edx ; 2^(32 + edx) / M = 2^33 / 66666667 = 5
shr eax,01fh
add edx,eax
invoke crt_printf,addr szFmt,edx mov ecx,dword ptr ds:[y] ; ecx = 10 / 6 = 1
mov eax,02AAAAAABh ; eax = 715827883
imul ecx
mov eax,edx ; 2^(32 + edx) / M = 2^32 / 2AAAAAAB = 6
shr eax,01fh
add edx,eax
invoke crt_printf,addr szFmt,edx mov ecx,dword ptr ds:[z] ; ecx = -10 / 9 = -1
mov eax,038E38E39h ; eax = 954437177
imul ecx
sar edx,1 ; 2^(32 + edx) / M = 2^33 / 38E38E39 = 9
mov ecx,edx
shr ecx,01fh
add edx,ecx
invoke crt_printf,addr szFmt,edx invoke ExitProcess,0
main ENDP
END main

先来看第一段汇编代码,我们此时已知M = 055555556h 且 edx = N带入公式2^(32+n) / M,由于edx没有变化所以此处应计算2^32 / 055555556h = 2.9999 即可计算出此处是除以的3

mov ecx,dword ptr ds:[y]      ; 被除数
mov eax,055555556h ; M值 => 此处的M模值是编译器计算后得到的
imul ecx
mov eax,edx ; edx = N
shr eax,01fh
add edx,eax
invoke crt_printf,addr szFmt,edx

再来看另一段,如下所示,这段代码中sar edx,1edx的值发生过一次变化,所以公式中应该加上变化的一次计算得到 2^33 / 66666667 = 5 除数是5

mov ecx,dword ptr ds:[y]       ; ecx = 10 / 5 = 2
mov eax,066666667h ; 此处的M模值是编译器计算后得到的
imul ecx
sar edx,1 ; 想要知道除数是多少,只需要
mov eax,edx ; 2^(32 + edx) / M = 2^33 / 66666667 = 5
shr eax,01fh
add edx,eax
invoke crt_printf,addr szFmt,edx

除数为正数非2的次幂优化(无符号): 上方代码中的除法计算是针对有符号数进行的,如果是针对无符号数则需要以下方式计算.

.data
x DWORD ?
y DWORD ?
z DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],-5
mov dword ptr ds:[y],10
mov dword ptr ds:[z],20 ; 除法(无符号)非2的次幂(正数)转换为乘法 xor edx,edx
mov ecx,dword ptr ds:[y] ; ecx = 10
mov eax,0AAAAAAABh ; ecx / 3 = 3
mul ecx
shr edx,1
invoke crt_printf,addr szFmt,edx ; 还原除数: 2 ^(32 + n) / M => 2 ^ (32+2) / 0CCCCCCCDh = 5
xor edx,edx
mov ecx,dword ptr ds:[y] ; ecx = 10 => 计算: 10/5
mov eax,0CCCCCCCDh ; eax = M
mul ecx
shr edx,2 ; edx= n
invoke crt_printf,addr szFmt,edx ; 还原除数: 2 ^(32 + n) / M => 2 ^ (32+2) / 0AAAAAAABh = 6
xor edx,edx
mov ecx,dword ptr ds:[y] ; ecx = 10 => 计算:10/6
mov eax,0AAAAAAABh ; eax = M
mul ecx
shr edx,2 ; edx = n
invoke crt_printf,addr szFmt,edx ;还原除数: 2 ^(32 + n) / M => 2 ^ 33 / 038E38E39h = 9
xor edx,edx
mov ecx,dword ptr ds:[z] ; ecx = 20 => 计算: 20/9
mov eax,038E38E39h ; eax = M
mul ecx
shr edx,1 ; edx = n
invoke crt_printf,addr szFmt,edx
invoke ExitProcess,0
main ENDP
END main

除数为负数非2的次幂优化(无符号):

.data
x DWORD ?
y DWORD ?
z DWORD ?
szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
main PROC
mov dword ptr ds:[x],-5
mov dword ptr ds:[y],10
mov dword ptr ds:[z],20
; 还原除数: 2 ^(32 + n) / M => 2 ^ 33 / 0AAAAAAABh = nge(3) => -3
xor edx,edx
mov ecx,dword ptr ds:[z] ; ecx = 20 => 计算: 20/-3
mov eax,0AAAAAAABh ; eax = M
mul ecx
shr edx,1 ; edx = n
neg edx ; edx=6 结果neg取反
invoke crt_printf,addr szFmt,edx ; 还原除数: 2 ^(32 + n) / M => 2 ^ 62 / 040000001h = 4294967292
xor edx,edx
mov ecx,dword ptr ds:[y] ; ecx = 10 => 计算: 10 / -3
mov eax,040000001h ; eax = M
mul ecx
shr edx,01eh ; edx = n
invoke crt_printf,addr szFmt,edx invoke ExitProcess,0
main ENDP
END main

看一下64位除法,编译后载入观察,一摸一样,不用再写了。

#include <stdio.h>
#include <windows.h> int main(int argc,char * argv[])
{
int x, y, z;
scanf("%d", &x); z = x / 3;
printf("%d \n", z); z = x / 5;
printf("%d \n", z); return 0;
}

看看 z = x / 5 + 3 - 2; 优化后变成了 z = x / 5 + 1 够毒。

继续改改。

int main(int argc,char * argv[])
{
int x, y, z;
scanf("%d", &x); z = x / 3;
printf("%d \n", z); z = x / 5 + 3 - 2;
y = z * x + 4; printf("%d \n", y); return 0;
}

再来看看64位版无符号数。

int main(int argc,char * argv[])
{
unsigned int x, y, z;
scanf("%d", &x); y = x / -3; printf("%d \n", y); z = x / -5;
printf("%d \n", z);
return 0;
}

还原除数:2^32+1E / 0x40000001 = 2^62 / 1073741825 = 4294967292.0000000037252902949925 = FFFFFFFC

还原除数:2^32+0x1F / 0x80000003 = 2^63 / 2147483651 = 4294967290.0000000083819031598299 = FFFFFFFA

C/C++ 反汇编:针对加减乘除的还原的更多相关文章

  1. AutoIT 测试GUI工具

    今天听到同事提到AutoIT,可以用来测试GUI窗口.了解一下这个工具. 以下内容引自: http://www.jb51.net/article/14870.htm (此url非原出处,该博主未注明原 ...

  2. 汇编与C语句

    ---恢复内容开始--- 汇编与C语句 4.1C语句与汇编 学习了汇编语言之后,就需要将常用的C语言代码结构与相应的汇编语言联系起来.这样就可以在分析汇编语言的时候,明白它的意思.C语言中函数过程的调 ...

  3. js处理浮点数计算误差

    众所周知,浮点计算会产生舍入误差的问题,比如,0.1+0.2,结果应该是0.3,但是计算的结果并不是如此,而是0.30000000000000004,这是使用基于IEEE754数值的浮点计算的通病,j ...

  4. git 一口气带你走完git之旅

    1.git是目前世界上最先进的分布式版本控制系统.svn是集成式版本控制系统,那么问题来了,什么叫分布式管理和集中式管理? 首先,svn 需要有一个中央服务器,协同开发者需要同中央服务器连接,所有的版 ...

  5. so easy, too happy

    一.预估与实际 PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 • Estimate • 估计这个任务需要多 ...

  6. Backup and Recovery Basics1

    一.Backup and Recovery Overview 1.Backup and Recovery Overview 1.1 What is Backup and Recovery? 一般,备份 ...

  7. MariaDB日志文件、备份与恢复

    1. 数据库的6种日志 数据库有6种日志,分别是:查询日志.慢查询日志.错误日志.二进制日志.中继日志以及事务日志. 1> 查询日志 查询日志记录每一条sql语句,建议不开启,因为如果访问量较大 ...

  8. MariaDB知识点总结02--日志+备份

    一.日志 1.查询日志 记录每一条sql语句,建议不开启,因为如果访问量较大,会占用相当大的资源,影响性能; vim /etc/my.cnf.d/server.cnf general_log = ON ...

  9. mariadb数据库备份与恢复

    1.查询日志: 记录每一条sql语句,建议不开启,因为如果访问量较大,会占用相当大的资源,影响性能; vim /etc/my.cnf.d/server.cnf general_log = ON| OF ...

  10. Devstack单节点环境实战配置

    本文为minxihou的翻译文章,转载请注明出处Bob Hou: http://blog.csdn.net/minxihou JmilkFan:minxihou的技术博文方向是 算法&Open ...

随机推荐

  1. Codeforces Round #732 (Div. 2) A ~ D 个人题解记录

    比赛链接:Here 1546A - AquaMoon and Two Arrays 选定两个数组元素执行以下操作: \(a_i,a_j (1\le i,j \le n)\) 一个 +1 另一个 -1, ...

  2. L3-008 喊山 (30 分) (BFS)

    喊山,是人双手围在嘴边成喇叭状,对着远方高山发出"喂-喂喂-喂喂喂--"的呼唤.呼唤声通过空气的传递,回荡于深谷之间,传送到人们耳中,发出约定俗成的"讯号",达 ...

  3. django的简单学习

    前言 以下项目实现基于一个投票系统 安装django 命令行安装 pip install django pycharm安装 pycharm的setting里找到这个,点击+号,搜索django 点击I ...

  4. mongodb导入本地json文件

  5. P1955【绿】

    这道题是标准的"离散化+并查集"模版题,通过这道题彻底理解了并查集,同时还意识到了我之前一直用map来实现离散化的方法其实是最简单但是最慢的方法,以这道题为例,map导致时间消耗有 ...

  6. C#查找算法1:二分查找

    二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法.但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列. 原理:将n个元素分成个数大致相同的两半,取 ...

  7. 基于html5+javascript技术开发的房贷利率计算器,买房的码农们戳进来

    房贷计算器是一款专为购房者设计的实用工具应用,其主要功能是帮助用户详细计算房贷的还款金额.利息以及还款计划等.通过这款软件,用户可以更加便捷地了解到自己的还款情况和计划,从而更好地规划自己的财务.下面 ...

  8. Linux查看文件内容与处理文件

    Linux查看文件内容与处理文件 目录 Linux查看文件内容与处理文件 查看文件内容 1.查看文件类型 2.查看整个文件 3.查看部分文件 处理文件 1.创建空文件 2.过滤文件内容 3.统计文件内 ...

  9. 状态: 失败 -测试失败: IO 错误: The Network Adapter could not establish the connection (CONNECTION_ID=BMRc/8PgR2+0i4PK2tnHQA==)

    1.问题 问题如标题所示,在使用Oracle SQL Developer连接时发现错误: 状态: 失败 -测试失败: IO 错误: The Network Adapter could not esta ...

  10. [转帖]Mnesia reports that this RabbitMQ cluster has experienced a network partition.

    一 问题描述 双节点RabbitMQ集群发生了脑裂,节点日志报错: [error] <0.6318.0> Mnesia(rabbit@pc2): ** ERROR ** mnesia_ev ...