可移植(主要是Linux和Windows系统)4字节提取/比较

4

向所有的C语言编程者问好。

在查找类似问题之前,我找不到一个相同的答案。

如何以可移植的方式提取/比较4字节(当然不能使用memcpy / memcmp)?

我从未学过C语言,因此我是一个活生生的例证,没有掌握基础知识,一切都变得一塌糊涂。无论如何,现在写作也不是说“从字母表开始”。

    ulHashPattern = *(unsigned long *)(pbPattern);
        for (a=0; a < ASIZE; a++) bm_bc[a]=cbPattern;
        for (j=0; j < cbPattern-1; j++) bm_bc[pbPattern[j]]=cbPattern-j-1;
        i=0;
        while (i <= cbTarget-cbPattern) {
            if ( *(unsigned long *)&pbTarget[i] == ulHashPattern ) {

上述片段在Windows 32位编译器上按预期工作。我希望所有这样的4对4比较在64位Windows和Linux下也能工作。 很多时候我需要2、4、8字节的传输,在上面的例子中,我需要从某个pbTarget偏移量显式地获取4字节。现在的问题是:我应该使用什么类型来替代unsigned long(我猜类似于UINT16、UINT32、UINT64的类型就可以了)。换句话说,我需要哪三种类型才能始终独立地表示2、4、8字节? 我认为这个基本问题会引起很多麻烦,所以应该澄清一下。
2012年1月16日补充:
@Richard J. Ross III
我有点困惑!因为我不知道Linux是否使用1]或2],即在Linux中是否定义了_STD_USING, 换句话说,哪一组类型uint8_t,...,uint64_t或_CSTD uint8_t,...,_CSTD uint64_t是可移植的?
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef _ULonglong uint64_t;

[2] 来自 MVS 10.0 的 stdint.h 摘录
 #if defined(_STD_USING)
...
using _CSTD uint8_t; using _CSTD uint16_t;
using _CSTD uint32_t; using _CSTD uint64_t;
...

使用 Microsoft C 32 位版本没有问题:
; 3401 :           if ( *(_CSTD uint32_t *)&pbTarget[i] == *(_CSTD uint32_t *)(pbPattern) )

  01360 8b 04 19     mov     eax, DWORD PTR [ecx+ebx]
  01363 8b 7c 24 14  mov     edi, DWORD PTR _pbPattern$GSCopy$[esp+1080]
  01367 3b 07        cmp     eax, DWORD PTR [edi]
  01369 75 2c        jne     SHORT $LN80@Railgun_Qu@6

但当目标代码为64位时,就会发生这种情况:
D:\_KAZE_Simplicius_Simplicissimus_Septupleton_r2-_strstr_SHORT-SHOWDOWN_r7>cl /Ox /Tcstrstr_SHORT-SHOWDOWN.c /Fastrstr_SHORT-SHOWDOWN /w /FAcs
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.30729.01 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strstr_SHORT-SHOWDOWN.c
strstr_SHORT-SHOWDOWN.c(1925) : fatal error C1083: Cannot open include file: 'stdint.h': No such file or directory

D:\_KAZE_Simplicius_Simplicissimus_Septupleton_r2-_strstr_SHORT-SHOWDOWN_r7>

“Linux的stdint.h头文件是否总是存在呢?”
“我没有放弃,注释掉了它://#include <stdint.h>,然后编译通过了。”
; 3401 :           if ( !memcmp(&pbTarget[i], &ulHashPattern, 4) ) 
  01766 49 63 c4     movsxd  rax, r12d
  01769 42 39 2c 10  cmp     DWORD PTR [rax+r10], ebp
  0176d 75 38        jne     SHORT $LN1@Railgun_Qu@6

; 3401 :           if ( *(unsigned long *)&pbTarget[i] == ulHashPattern ) 
  01766 49 63 c4     movsxd  rax, r12d
  01769 42 39 2c 10  cmp     DWORD PTR [rax+r10], ebp
  0176d 75 38        jne     SHORT $LN1@Railgun_Qu@6

这个“unsigned long *”让我困扰,因为gcc -m64会获取QWORD而不是DWORD,对吗?
@Mysticial 只想展示Microsoft CL 32位v16完成的三种不同的翻译: 1]
; 3400 :           if ( !memcmp(&pbTarget[i], pbPattern, 4) )
  01360 8b 04 19     mov     eax, DWORD PTR [ecx+ebx]
  01363 8b 7c 24 14  mov     edi, DWORD PTR _pbPattern$GSCopy$[esp+1080]
  01367 3b 07        cmp     eax, DWORD PTR [edi]
  01369 75 2c        jne     SHORT $LN84@Railgun_Qu@6

[2]
; 3400 :           if ( !memcmp(&pbTarget[i], &ulHashPattern, 4) )
  01350 8b 44 24 14  mov     eax, DWORD PTR _ulHashPattern$[esp+1076]
  01354 39 04 2a     cmp     DWORD PTR [edx+ebp], eax
  01357 75 2e        jne     SHORT $LN83@Railgun_Qu@6

3]

; 3401 :           if ( *(uint32_t *)&pbTarget[i] == ulHashPattern )
  01350 8b 44 24 14  mov     eax, DWORD PTR _ulHashPattern$[esp+1076]
  01354 39 04 2a     cmp     DWORD PTR [edx+ebp], eax
  01357 75 2e        jne     SHORT $LN79@Railgun_Qu@6

最初的目标是通过单个mov指令(分别使用*(uint32_t *)&pbTarget[i])提取并比较4字节和一个寄存器变量的4字节长度,即在单个指令中进行一次RAM访问和一次比较。 令人不快的是,我只成功将memcmp()在pbPattern上的3次RAM访问(指向4个或更多字节)减少到2次,这要归功于内联。 如果我想在pbPattern的前4个字节上使用memcmp()(如2所示),则ulHashPattern不应该是寄存器类型,而3则不需要这样的限制。
; 3400 :           if ( !memcmp(&pbTarget[i], &ulHashPattern, 4) )

上面的行出现了错误(ulHashPattern被定义为:register unsigned long ulHashPattern;):
strstr_SHORT-SHOWDOWN.c(3400) : error C2103: '&' on register variable

是的,你说得对:memcmp()可以解决这个问题(但有局限性)——第2节与第3节我的代码风格完全相同。显然,当我可能手动编码时不使用函数的倾向已经过去了,但我喜欢它。
虽然我还不完全满意编译器,我已将ulHashPattern定义为寄存器变量,但它每次仍从RAM加载?也许我错过了什么,但我认为这行代码(mov eax,DWORD PTR _ulHashPattern$[esp+1076])非常丑陋,会降低性能。

请查看在stdint.h中定义的uint64_t及其对应类型。 - Richard J. Ross III
谢谢Richard J. Ross III,我回家后会寻找这些对应项,也许因为它们在stdint.h中,所以它们是可移植的,对吧? - Georgi
1个回答

1

严格来说,你唯一能使用的类型是char。这是因为你正在违反strict-aliasing以下类型转换:

*(unsigned long *)(pbPattern);
*(unsigned long *)&pbTarget[i]

char*是唯一的例外,因为您可以使用char*别名任何数据类型。

如果您在GCC上打开警告,您应该会在代码片段中收到严格别名警告。(据我所知,MSVC不会警告严格别名。)


我无法确定您在代码片段中尝试做什么,但是这个想法仍然适用,您不应该使用unsigned long或任何其他数据类型来加载和比较不同类型的大块数据。

实际上,您真的应该使用memcmp(),因为它很直观,并且可以让您避免将所有内容强制转换为char*的低效率。

您不能使用memcmp()的原因是什么?


如果您可以接受违反严格别名规则,您可以使用在<stdint.h>中定义的固定整数类型(例如uint32_t)。但是,请注意这些类型是按位数而不是字节数来固定的。


谢谢你的帮助,哈哈一个双关语,如果你知道我制造了多少个双关语,他们应该称呼我为“双关语人”。我担心我的编码风格非常违反规定(不仅仅是在严格别名方面)。至于你的问题:memcmp()不可能使用,因为我想要速度,实际上这个4vs4比较是内联memcmp()的开始。 - Georgi
同意,但在这种情况下,经过多次尝试和错误(以及许多基准测试),我想出了比memcmp更快的代码:[链接](http://www.sanmayce.com/Railgun/index.html) - Georgi
一样,我仍然把Windows作为主要操作系统。但是当需要时,我会通过双启动使用Linux。不过没错,我从未见过任何Windows编译器生成strict-aliasing警告。所以我能理解你最初陷入这种情况的原因。尽管迄今为止,我只在与内联汇编混合使用时看到strict-aliasing违规实际上引起不良行为的情况。 - Mysticial
你感受到了我的困境,在不久的将来(几天后),也许我会问一个类似的问题,关于GCC警告,这些警告像倾盆大雨一样涌现(对于通过CL而没有任何警告的简单代码),希望你能再次帮助我。 - Georgi
哦,Mysticial,我刚访问了你的个人资料,被震撼了,伙计!最近我在一个帖子中使用了你的杰出成就作为无限的例子。我们的世界确实很小,你是真正的人才 - 致以最好的问候。 - Georgi
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接