<返回更多

使用数学的力量来简化多级比较

2022-08-26  网易号  漫漫开发路
加入收藏

今天的标题确实是无聊。

有些时候,你可能需要进行一项多级计算。最为常见的例子是对一个包含有主版本号和次版本号的版本信息进行检查。错误的版本号检查是最常见的错误来源之一。

如果你正在编写代码检查版本数字,则可以使用VerifyVersionInfo这个函数,我在此假设你正在为windows 2000及更新版本的Windows操作系统编写代码。

您可以将待比较的数值打包到单个比较操作中,而不是编写多级比较。我们先考虑下面的代码:
inline unsigned __int64
MakeUINT64(Dword Low, DWORD High)
{
ULARGE_INTEGER Value;
Value.LowPart = Low;
Value.HighPart = High;
return Value.QuadPart;
}
BOOL IsVersionAtLeast(DWORD Major, DWORD Minor,
DWORD MajorDesired, DWORD MinorDesired)
{
return MakeUINT64(Minor, Major) >= MakeUINT64(MinorDesired, MajorDesired);
}
这里发生了什么呢?

我们将2个32位的值合并到一个更大的64位的值中,并将最重要的部分放在高位部分,将不太重要的部分放在低位部分。
然后我们坐下来,让数学的力量为我们工作。 如果你还记得小学的比较规则,你会发现它们完全符合我们要应用于多级比较的规则。 比较主要值; 如果不同,那就是结果。 否则,比较次要值。
如果你还是不信,让我们看看生成的汇编代码:
00000 8b 44 24 04 mov eax, DWORD PTR _Major$[esp-4]
00004 3b 44 24 0c cmp eax, DWORD PTR _MajorDesired$[esp-4]
00008 8b 4c 24 08 mov ecx, DWORD PTR _Minor$[esp-4]
0000c 8b 54 24 10 mov edx, DWORD PTR _MinorDesired$[esp-4]
00010 72 0b jb SHORT $L48307
00012 77 04 ja SHORT $L48317
00014 3b ca cmp ecx, edx
00016 72 05 jb SHORT $L48307
$L48317:
00018 33 c0 xor eax, eax
0001a 40 inc eax
0001b eb 02 jmp SHORT $L48308
$L48307:
0001d 33 c0 xor eax, eax
$L48308:
0001f c2 10 00 ret 16 ; 00000010H

编译器生成的代码等价于下面的代码:
BOOL IsVersionAtLeastEquiv(DWORD Major, DWORD Minor,
DWORD MajorDesired, DWORD MinorDesired)
{
if (Major < MajorDesired) return FALSE; if (Major > MajorDesired) return TRUE;
if (Minor < MinorDesired) return FALSE; return TRUE; } 实际上,如果你以(容易出错的)老式方式编写代码,你会得到: BOOL IsVersionAtLeast2(DWORD Major, DWORD Minor, DWORD MajorDesired, DWORD MinorDesired) { return Major > MajorDesired ||
(Major == MajorDesired && Minor >= MinorDesired);
}
00000 55 push ebp
00001 8b ec mov ebp, esp
00003 8b 45 08 mov eax, DWORD PTR _Major$[ebp]
00006 3b 45 10 cmp eax, DWORD PTR _MajorDesired$[ebp]
00009 77 0e ja SHORT $L48329
0000b 75 08 jne SHORT $L48328
0000d 8b 45 0c mov eax, DWORD PTR _Minor$[ebp]
00010 3b 45 14 cmp eax, DWORD PTR _MinorDesired$[ebp]
00013 73 04 jae SHORT $L48329
$L48328:
00015 33 c0 xor eax, eax
00017 eb 03 jmp SHORT $L48330
$L48329:
00019 33 c0 xor eax, eax
0001b 40 inc eax
$L48330:
0001c 5d pop ebp
0001d c2 10 00 ret 16 ; 00000010H

如你所见,这在功能上与之前的两个版本相同。

你也可以将值打包成更小的单位,前提是你知道不会出现溢出或截断。 例如,如果你知道 Major 和 Minor 值永远不会超过 65535,则可以使用以下代码:
BOOL SmallIsVersionAtLeast(WORD Major, WORD Minor,
WORD MajorDesired, WORD MinorDesired)
{
return MAKELONG(Minor, Major) >= MAKELONG(MinorDesired, MajorDesired);
}
00000 0f b7 44 24 0c movzx eax, WORD PTR _MajorDesired$[esp-4]
00005 0f b7 4c 24 10 movzx ecx, WORD PTR _MinorDesired$[esp-4]


0000a 0f b7 54 24 08 movzx edx, WORD PTR _Minor$[esp-4]
0000f c1 e0 10 shl eax, 16 ; 00000010H
00012 0b c1 or eax, ecx
00014 0f b7 4c 24 04 movzx ecx, WORD PTR _Major$[esp-4]
00019 c1 e1 10 shl ecx, 16 ; 00000010H
0001c 0b ca or ecx, edx
0001e 33 d2 xor edx, edx
00020 3b c8 cmp ecx, eax
00022 0f 9d c2 setge dl
00025 8b c2 mov eax, edx
00027 c2 10 00 ret 16 ; 00000010H

如果你知道版本永远不会超过 255,那么你可以做得更小:
BOOL TinyIsVersionAtLeast(BYTE Major, BYTE Minor,
BYTE MajorDesired, BYTE MinorDesired)
{
return MAKEWORD(Minor, Major) >= MAKEWORD(MinorDesired, MajorDesired);
}
00000 33 c0 xor eax, eax
00002 8a 64 24 0c mov ah, BYTE PTR _MajorDesired$[esp-4]
00006 33 c9 xor ecx, ecx
00008 8a 6c 24 04 mov ch, BYTE PTR _Major$[esp-4]
0000c 8a 44 24 10 mov al, BYTE PTR _MinorDesired$[esp-4]
00010 8a 4c 24 08 mov cl, BYTE PTR _Minor$[esp-4]
00014 66 3b c8 cmp cx, ax
00017 1b c0 sbb eax, eax
00019 40 inc eax
0001a c2 10 00 ret 16 ; 00000010H

如果原始版本可以正常工作,我们为什么要改它呢? 因为你可能想要进行三向或四向比较,并且将值包装得更小可以让你将更多数值放入比较代码中。
BOOL IsVersionBuildAtLeast(
WORD Major, WORD Minor, DWORD Build,
WORD MajorDesired, WORD MinorDesired, DWORD BuildDesired)
{
return MakeUINT64(Build, MAKELONG(Minor, Major)) >=
MakeUINT64(Build, MAKELONG(MinorDesired, MajorDesired));
}

通过将主要版本、次要版本和内部版本号打包成一个 64 位值,一个比较操作将同时比较所有三个。 将此与你通常必须编写的复杂(并且代码难以理解)的比较链进行比较:
return Major > MajorDesired ||
(Major == MajorDesired &&
(Minor >= MinorDesired ||
(Minor == MinorDesired && Build >= BuildDesired)));

总结

手工撸代码固然香,但机器的时代来临了:机器的事情,请交给机器。
我们可以将时间花费在设计上,这事儿,机器它干不了。

最后

Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Using the powers of mathematics to simplify multi-level comparisons》

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>