关闭优化时未解析的外部符号 __aullshr
Unresolved external symbol __aullshr when optimization is turned off
我正在使用 Visual Studio 2015 C/C++ 编译器编译一段 UEFI C 代码。
编译器针对 IA32,而非 X64。
使用“/O1”打开优化时,构建正常。
当关闭使用“/Od”的优化时,构建给出以下错误:
error LNK2001: unresolved external symbol __aullshr
根据here,有一个解释为什么这种函数可以被编译器隐式调用:
It turns out that this function is one of several compiler support
functions that are invoked explicitly by the Microsoft C/C++ compiler.
In this case, this function is called whenever the 32-bit compiler
needs to multiply two 64-bit integers together. The EDK does not link
with Microsoft's libraries and does not provide this function.
Are there other functions like this one? Sure, several more for 64-bit
division, remainder and shifting.
但是根据here:
...Compilers that implement intrinsic functions generally enable them
only when a program requests optimization...
所以当我用 /Od
明确关闭优化时,这些函数怎么仍然被调用??
添加 1 - 2:32 2019 年 2 月 16 日下午
看来我对 __aullshr
函数的理解是错误的。
不是编译器内部函数根据here,原来是一个运行库函数,其实现可以在:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\intel\ullshr.asm
或 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\i386\ullshr.asm
这样的VC运行时函数是由编译器引入的,供32位应用程序执行64位操作。
但是我还是不知道为什么/O1
可以build pass,而/Od
却失败了?
似乎优化开关会影响 VC 运行时库的使用。
添加 2 - 4:59 2019 年 2 月 17 日下午
我找到了导致构建失败的代码。
原来是一些C结构体位域操作。有一个 64 位 C 结构,它有很多由单个 UINT64 变量支持的位字段。当我注释掉访问这些位域的单行代码时,构建就通过了。当指定 /Od
时,似乎 _aullshr()
函数用于访问这些位字段。
由于这是固件代码的一部分,我想知道使用 /Od
关闭优化是否是一个好习惯?
添加 3 - 9:33 2019 年 2 月 18 日上午
我在下面为 VS2015 创建了最小的可重现示例。
首先,有一个静态库项目:
(test.c)
typedef unsigned __int64 UINT64;
typedef union {
struct {
UINT64 field1 : 16;
UINT64 field2 : 16;
UINT64 field3 : 6;
UINT64 field4 : 15;
UINT64 field5 : 2;
UINT64 field6 : 1;
UINT64 field7 : 1;
UINT64 field8 : 1; //<=========
UINT64 field9 : 1;
UINT64 field10 : 1;
UINT64 field11 : 1;
UINT64 field12 : 1; //<=========
UINT64 field13 : 1;
UINT64 field14 : 1;
} Bits;
UINT64 Data;
} ISSUE_STRUCT;
int
Method1
(
UINT64 Data
)
{
ISSUE_STRUCT IssueStruct;
IssueStruct.Data = Data;
if (IssueStruct.Bits.field8 == 1 && IssueStruct.Bits.field12 == 1) { // <==== HERE
return 1;
}
else
{
return 0;
}
}
然后是一个Windows DLL工程:
(DllMain.c)
#include <Windows.h>
typedef unsigned __int64 UINT64;
int
Method1
(
UINT64 Data
);
int __stdcall DllMethod1
(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpReserved
)
{
if (Method1(1234)) //<===== Use the Method1 from the test.obj
{
return 1;
}
return 2;
}
构建过程:
首先编译test.obj:
cl.exe /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL
/EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl test.c
(add: VC++ 2015 编译器针对 test.obj
:
发出以下警告
warning C4214: nonstandard extension used: bit field types other than
int
)
然后编译DllMain.obj:
cl /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL
/EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl DllMain.c
然后linkDllMain.obj到test.obj
link DllMain.obj ..\aullshr\test.obj /NOLOGO /NODEFAULTLIB
/IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D
/SECTION:.pdata,D /MACHINE:X86 /LTCG /SAFESEH:NO /DLL
/ENTRY:DllMethod1 /DRIVER
它会给出以下错误:
Generating code Finished generating code test.obj : error LNK2001:
unresolved external symbol __aullshr DllMain.dll : fatal error
LNK1120: 1 unresolved externals
如果我在test.c中删除HERE处的位域操作代码,link错误将消失。
如果我只从 test.c 的编译选项中删除 /Od,则link 错误将消失。
添加 4 - 12:40 2019 年 2 月 18 日下午
感谢@PeterCordes 在他的评论中,有一种更简单的方法可以重现此问题。只需调用以下方法:
uint64_t shr(uint64_t a, unsigned c) { return a >> c; }
然后使用以下命令编译源代码:
cl /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL
/EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl DllMain.c
link DllMain.obj /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF
/OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D
/MACHINE:X86 /LTCG /SAFESEH:NO /DLL /ENTRY:DllMethod1 /DRIVER
此问题可以重现于:
Microsoft (R) C/C++ 针对 x86 (VS2013) 优化编译器版本 18.00.40629
Microsoft (R) C/C++ 针对 x86 (VS2015) 优化编译器版本 19.00.24210
Microsoft (R) C/C++ 针对 x86 (VS2015) 优化编译器版本 19.00.24215.1
根据 UEFI coding standard 5.6.3.4 Bit Fields 中的规定:
Bit fields may only be of type INT32, signed INT32, UINT32, or a
typedef name defined as one of the three INT32 variants.
所以我最终的解决方案是修改 UEFI 代码以使用 UINT32
而不是 UINT64
。
您所描述的似乎是以下情况之一:
编译器错误仅由 /Od
触发。如果您能在展示问题的最小程序中提取结构定义和违规代码,以供专家调查问题,那将非常有帮助。
编译器安装问题:您可能正在链接到与您的 C 编译器不兼容的 C 库。这可能会在程序的其他区域引起更多问题。我强烈建议您从头开始重新安装编译器。
用于创建 UEFI 应用程序的构建设置省略了 MSVC 的代码生成器期望可用的辅助函数静态库。 MSVC 的代码生成有时会插入对辅助函数的调用,就像 gcc 在 32 位平台上对 64x64 乘法或除法或其他各种操作所做的一样。 (例如,没有硬件 popcnt 的目标上的 popcount。)
在这种情况下,将 MSVC 手持到不那么愚蠢的代码生成(本身就是一件好事)恰好会删除代码库中所有辅助函数的使用。这很好,但不会修复您的构建设置。 如果您以后添加需要助手的代码,它可能会再次崩溃。 uint64_t shr(uint64_t a, unsigned c) { return a >> c; }
即使在 -O2
.
也会编译以包含对辅助函数的调用
在没有优化的情况下按常量移位使用 _aullshr
,而不是内联 shrd
/ shr
。 这个确切的问题(损坏的 -Od
构建)将在 uint64_t x
中重复出现; x >> 4
或您来源中的内容。
(我不知道 MSVC 在哪里保存它的辅助函数库。我们认为它是一个静态库,你可以 link 而无需引入 DLL 依赖项(UEFI 不可能),但我们不知道知道它是否可能与一些 CRT 启动代码捆绑在一起,您需要避免 link 使用 UEFI。)
这个例子很清楚未优化与优化的问题。优化后的 MSVC 不需要辅助函数,但它的脑残 -Od
代码 需要 .
对于位域访问,MSVC 显然使用了位域成员基类型的右移。在您的情况下,您将其设为 64 位类型,而 32 位 x86 没有 64 位整数移位(使用 MMX 或 SSE2 除外)。使用 -Od
即使对于常量计数,它也会将数据放入 EDX:EAX,将移位计数放入 cl
(就像 x86 移位指令一样),然后调用 __aullshr
.
__a
= ??
ull
= unsigned long long.
shr
= 右移(类似于同名的 x86 asm 指令)。
- 它采用
cl
中的移位计数,与 x86 移位指令完全一样。
From the Godbolt compiler explorer, x86 MSVC 19.16 -Od
,位域成员类型为 UINT64
。
;; from int Method1(unsigned __int64) PROC
...
; extract IssueStruct.Bits.field8
mov eax, DWORD PTR _IssueStruct$[ebp]
mov edx, DWORD PTR _IssueStruct$[ebp+4]
mov cl, 57 ; 00000039H
call __aullshr ; emulation of shr edx:eax, cl
and eax, 1
and edx, 0
;; then store that to memory and cmp/jcc both halves. Ultra braindead
显然对于常量移位和仅访问 1 位,这很容易优化,因此 MSVC 实际上并没有在 -O2
处调用辅助函数。不过,它仍然效率很低!它无法完全优化基本类型的 64 位,即使位域的 none 比 32 位宽。
; x86 MSVC 19.16 -O2 with unsigned long long as the bitfield type
int Method1(unsigned __int64) PROC ; Method1, COMDAT
mov edx, DWORD PTR _Data$[esp] ; load the high half of the inputs arg
xor eax, eax ; zero the low half?!?
mov ecx, edx ; copy the high half
and ecx, 33554432 ; 02000000H ; isolate bit 57
or eax, ecx ; set flags from low |= high
je SHORT $LN2@Method1
and edx, 536870912 ; 20000000H ; isolate bit 61
xor eax, eax ; re-materialize low=0 ?!?
or eax, edx ; set flags from low |= high
je SHORT $LN2@Method1
mov eax, 1
ret 0
$LN2@Method1:
xor eax, eax
ret 0
int Method1(unsigned __int64) ENDP ; Method1
显然,为下半部分实现 0
而不是忽略它真的很愚蠢。 如果我们将位域成员类型更改为 unsigned
,MSVC 会做得更好。 (在 Godbolt link 中,我将其更改为 bf_t
以便我可以使用与 UINT64 分开的 typedef,为另一个联合成员保留它。)
使用基于 unsigned field : 1
位域成员的结构,MSVC 不需要 -Od
处的助手
它甚至可以在 -O2
、 处生成更好的代码,因此您绝对应该在实际生产代码中这样做。 仅对需要大于 32 位的字段使用 uint64_t
或 unsigned long long
成员, 如果您关心 MSVC 上的性能,它显然存在优化错误位域成员使用 64 位类型。
;; MSVC -O2 with plain unsigned (or uint32_t) bitfield members
int Method1(unsigned __int64) PROC ; Method1, COMDAT
mov eax, DWORD PTR _Data$[esp]
test eax, 33554432 ; 02000000H
je SHORT $LN2@Method1
test eax, 536870912 ; 20000000H
je SHORT $LN2@Method1
mov eax, 1
ret 0
$LN2@Method1:
xor eax, eax
ret 0
int Method1(unsigned __int64) ENDP ; Method1
我可能已经像 ((high >> 25) & (high >> 29)) & 1
一样用 2 shr
条指令和 2 条 and
条指令(和一条 mov
)无分支地实现了它。但是,如果它真的是可预测的,那么分支是合理的并且打破了数据依赖性。不过,clang 在这里做得很好,使用 not
+ test
一次测试两个位。 (并且 setcc
再次将结果作为整数)。这比我的想法有更好的延迟,特别是在没有移动消除的 CPU 上。 clang 也没有遗漏基于 64 位类型的位域优化。无论哪种方式,我们都会得到相同的代码。
# clang7.0 -O3 -m32 regardless of bitfield member type
Method1(unsigned long long): # @Method1(unsigned long long)
mov ecx, dword ptr [esp + 8]
xor eax, eax # prepare for setcc
not ecx
test ecx, 570425344 # 0x22000000
sete al
ret
UEFI 编码标准:
The EDK II coding standard 5.6.3.4 Bit Fields 表示:
- 位字段只能是
INT32
类型、带符号 INT32
、UINT32
或定义为三个 INT32
变体之一的 typedef 名称。
我不知道他们为什么要编这些 "INT32" 名字,而 C99 已经有了完美的 int32_t
。还不清楚他们为什么要施加这种限制。也许是因为 MSVC 错过了优化错误?或者可能通过禁止某些 "weird stuff".
来帮助人类程序员理解
gcc 和 clang 不会将 unsigned long long
作为位域类型发出警告,即使在 32 位模式和 -Wall -Wextra -Wpedantic
下,在 C 或 C++ 模式下也是如此。我认为 ISO C 或 ISO C++ 没有问题。
此外,Should use of bit-fields of type int be discouraged? 指出不应将普通的 int
作为位域类型,因为签名是实现定义的。并且 ISO C++ 标准讨论了从 char
到 long long
.
的位域类型
我认为您的 MSVC 关于非 int
位域的警告必须来自某种编码标准强制执行包,因为 Godbolt 上的普通 MSVC 即使使用 `-Wall 也不会这样做。
warning C4214: nonstandard extension used: bit field types other than int
我正在使用 Visual Studio 2015 C/C++ 编译器编译一段 UEFI C 代码。
编译器针对 IA32,而非 X64。
使用“/O1”打开优化时,构建正常。
当关闭使用“/Od”的优化时,构建给出以下错误:
error LNK2001: unresolved external symbol __aullshr
根据here,有一个解释为什么这种函数可以被编译器隐式调用:
It turns out that this function is one of several compiler support functions that are invoked explicitly by the Microsoft C/C++ compiler. In this case, this function is called whenever the 32-bit compiler needs to multiply two 64-bit integers together. The EDK does not link with Microsoft's libraries and does not provide this function.
Are there other functions like this one? Sure, several more for 64-bit division, remainder and shifting.
但是根据here:
...Compilers that implement intrinsic functions generally enable them only when a program requests optimization...
所以当我用 /Od
明确关闭优化时,这些函数怎么仍然被调用??
添加 1 - 2:32 2019 年 2 月 16 日下午
看来我对 __aullshr
函数的理解是错误的。
不是编译器内部函数根据here,原来是一个运行库函数,其实现可以在:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\intel\ullshr.asm
或 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\i386\ullshr.asm
这样的VC运行时函数是由编译器引入的,供32位应用程序执行64位操作。
但是我还是不知道为什么/O1
可以build pass,而/Od
却失败了?
似乎优化开关会影响 VC 运行时库的使用。
添加 2 - 4:59 2019 年 2 月 17 日下午
我找到了导致构建失败的代码。
原来是一些C结构体位域操作。有一个 64 位 C 结构,它有很多由单个 UINT64 变量支持的位字段。当我注释掉访问这些位域的单行代码时,构建就通过了。当指定 /Od
时,似乎 _aullshr()
函数用于访问这些位字段。
由于这是固件代码的一部分,我想知道使用 /Od
关闭优化是否是一个好习惯?
添加 3 - 9:33 2019 年 2 月 18 日上午
我在下面为 VS2015 创建了最小的可重现示例。
首先,有一个静态库项目:
(test.c)
typedef unsigned __int64 UINT64;
typedef union {
struct {
UINT64 field1 : 16;
UINT64 field2 : 16;
UINT64 field3 : 6;
UINT64 field4 : 15;
UINT64 field5 : 2;
UINT64 field6 : 1;
UINT64 field7 : 1;
UINT64 field8 : 1; //<=========
UINT64 field9 : 1;
UINT64 field10 : 1;
UINT64 field11 : 1;
UINT64 field12 : 1; //<=========
UINT64 field13 : 1;
UINT64 field14 : 1;
} Bits;
UINT64 Data;
} ISSUE_STRUCT;
int
Method1
(
UINT64 Data
)
{
ISSUE_STRUCT IssueStruct;
IssueStruct.Data = Data;
if (IssueStruct.Bits.field8 == 1 && IssueStruct.Bits.field12 == 1) { // <==== HERE
return 1;
}
else
{
return 0;
}
}
然后是一个Windows DLL工程:
(DllMain.c)
#include <Windows.h>
typedef unsigned __int64 UINT64;
int
Method1
(
UINT64 Data
);
int __stdcall DllMethod1
(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpReserved
)
{
if (Method1(1234)) //<===== Use the Method1 from the test.obj
{
return 1;
}
return 2;
}
构建过程:
首先编译test.obj:
cl.exe /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl test.c
(add: VC++ 2015 编译器针对 test.obj
:
warning C4214: nonstandard extension used: bit field types other than int
)
然后编译DllMain.obj:
cl /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl DllMain.c
然后linkDllMain.obj到test.obj
link DllMain.obj ..\aullshr\test.obj /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /SAFESEH:NO /DLL /ENTRY:DllMethod1 /DRIVER
它会给出以下错误:
Generating code Finished generating code test.obj : error LNK2001: unresolved external symbol __aullshr DllMain.dll : fatal error LNK1120: 1 unresolved externals
如果我在test.c中删除HERE处的位域操作代码,link错误将消失。
如果我只从 test.c 的编译选项中删除 /Od,则link 错误将消失。
添加 4 - 12:40 2019 年 2 月 18 日下午
感谢@PeterCordes 在他的评论中,有一种更简单的方法可以重现此问题。只需调用以下方法:
uint64_t shr(uint64_t a, unsigned c) { return a >> c; }
然后使用以下命令编译源代码:
cl /nologo /arch:IA32 /c /GS- /W4 /Gs32768 /D UNICODE /O1b2 /GL /EHs-c- /GR- /GF /Gy /Zi /Gm /Gw /Od /Zl DllMain.c
link DllMain.obj /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /SAFESEH:NO /DLL /ENTRY:DllMethod1 /DRIVER
此问题可以重现于:
Microsoft (R) C/C++ 针对 x86 (VS2013) 优化编译器版本 18.00.40629
Microsoft (R) C/C++ 针对 x86 (VS2015) 优化编译器版本 19.00.24210
Microsoft (R) C/C++ 针对 x86 (VS2015) 优化编译器版本 19.00.24215.1
根据 UEFI coding standard 5.6.3.4 Bit Fields 中的规定:
Bit fields may only be of type INT32, signed INT32, UINT32, or a typedef name defined as one of the three INT32 variants.
所以我最终的解决方案是修改 UEFI 代码以使用 UINT32
而不是 UINT64
。
您所描述的似乎是以下情况之一:
编译器错误仅由
/Od
触发。如果您能在展示问题的最小程序中提取结构定义和违规代码,以供专家调查问题,那将非常有帮助。编译器安装问题:您可能正在链接到与您的 C 编译器不兼容的 C 库。这可能会在程序的其他区域引起更多问题。我强烈建议您从头开始重新安装编译器。
用于创建 UEFI 应用程序的构建设置省略了 MSVC 的代码生成器期望可用的辅助函数静态库。 MSVC 的代码生成有时会插入对辅助函数的调用,就像 gcc 在 32 位平台上对 64x64 乘法或除法或其他各种操作所做的一样。 (例如,没有硬件 popcnt 的目标上的 popcount。)
在这种情况下,将 MSVC 手持到不那么愚蠢的代码生成(本身就是一件好事)恰好会删除代码库中所有辅助函数的使用。这很好,但不会修复您的构建设置。 如果您以后添加需要助手的代码,它可能会再次崩溃。 uint64_t shr(uint64_t a, unsigned c) { return a >> c; }
即使在 -O2
.
在没有优化的情况下按常量移位使用 _aullshr
,而不是内联 shrd
/ shr
。 这个确切的问题(损坏的 -Od
构建)将在 uint64_t x
中重复出现; x >> 4
或您来源中的内容。
(我不知道 MSVC 在哪里保存它的辅助函数库。我们认为它是一个静态库,你可以 link 而无需引入 DLL 依赖项(UEFI 不可能),但我们不知道知道它是否可能与一些 CRT 启动代码捆绑在一起,您需要避免 link 使用 UEFI。)
这个例子很清楚未优化与优化的问题。优化后的 MSVC 不需要辅助函数,但它的脑残 -Od
代码 需要 .
对于位域访问,MSVC 显然使用了位域成员基类型的右移。在您的情况下,您将其设为 64 位类型,而 32 位 x86 没有 64 位整数移位(使用 MMX 或 SSE2 除外)。使用 -Od
即使对于常量计数,它也会将数据放入 EDX:EAX,将移位计数放入 cl
(就像 x86 移位指令一样),然后调用 __aullshr
.
__a
= ??ull
= unsigned long long.shr
= 右移(类似于同名的 x86 asm 指令)。- 它采用
cl
中的移位计数,与 x86 移位指令完全一样。
From the Godbolt compiler explorer, x86 MSVC 19.16 -Od
,位域成员类型为 UINT64
。
;; from int Method1(unsigned __int64) PROC
...
; extract IssueStruct.Bits.field8
mov eax, DWORD PTR _IssueStruct$[ebp]
mov edx, DWORD PTR _IssueStruct$[ebp+4]
mov cl, 57 ; 00000039H
call __aullshr ; emulation of shr edx:eax, cl
and eax, 1
and edx, 0
;; then store that to memory and cmp/jcc both halves. Ultra braindead
显然对于常量移位和仅访问 1 位,这很容易优化,因此 MSVC 实际上并没有在 -O2
处调用辅助函数。不过,它仍然效率很低!它无法完全优化基本类型的 64 位,即使位域的 none 比 32 位宽。
; x86 MSVC 19.16 -O2 with unsigned long long as the bitfield type
int Method1(unsigned __int64) PROC ; Method1, COMDAT
mov edx, DWORD PTR _Data$[esp] ; load the high half of the inputs arg
xor eax, eax ; zero the low half?!?
mov ecx, edx ; copy the high half
and ecx, 33554432 ; 02000000H ; isolate bit 57
or eax, ecx ; set flags from low |= high
je SHORT $LN2@Method1
and edx, 536870912 ; 20000000H ; isolate bit 61
xor eax, eax ; re-materialize low=0 ?!?
or eax, edx ; set flags from low |= high
je SHORT $LN2@Method1
mov eax, 1
ret 0
$LN2@Method1:
xor eax, eax
ret 0
int Method1(unsigned __int64) ENDP ; Method1
显然,为下半部分实现 0
而不是忽略它真的很愚蠢。 如果我们将位域成员类型更改为 unsigned
,MSVC 会做得更好。 (在 Godbolt link 中,我将其更改为 bf_t
以便我可以使用与 UINT64 分开的 typedef,为另一个联合成员保留它。)
使用基于 unsigned field : 1
位域成员的结构,MSVC 不需要 -Od
它甚至可以在 -O2
、 处生成更好的代码,因此您绝对应该在实际生产代码中这样做。 仅对需要大于 32 位的字段使用 uint64_t
或 unsigned long long
成员, 如果您关心 MSVC 上的性能,它显然存在优化错误位域成员使用 64 位类型。
;; MSVC -O2 with plain unsigned (or uint32_t) bitfield members
int Method1(unsigned __int64) PROC ; Method1, COMDAT
mov eax, DWORD PTR _Data$[esp]
test eax, 33554432 ; 02000000H
je SHORT $LN2@Method1
test eax, 536870912 ; 20000000H
je SHORT $LN2@Method1
mov eax, 1
ret 0
$LN2@Method1:
xor eax, eax
ret 0
int Method1(unsigned __int64) ENDP ; Method1
我可能已经像 ((high >> 25) & (high >> 29)) & 1
一样用 2 shr
条指令和 2 条 and
条指令(和一条 mov
)无分支地实现了它。但是,如果它真的是可预测的,那么分支是合理的并且打破了数据依赖性。不过,clang 在这里做得很好,使用 not
+ test
一次测试两个位。 (并且 setcc
再次将结果作为整数)。这比我的想法有更好的延迟,特别是在没有移动消除的 CPU 上。 clang 也没有遗漏基于 64 位类型的位域优化。无论哪种方式,我们都会得到相同的代码。
# clang7.0 -O3 -m32 regardless of bitfield member type
Method1(unsigned long long): # @Method1(unsigned long long)
mov ecx, dword ptr [esp + 8]
xor eax, eax # prepare for setcc
not ecx
test ecx, 570425344 # 0x22000000
sete al
ret
UEFI 编码标准:
The EDK II coding standard 5.6.3.4 Bit Fields 表示:
- 位字段只能是
INT32
类型、带符号INT32
、UINT32
或定义为三个INT32
变体之一的 typedef 名称。
我不知道他们为什么要编这些 "INT32" 名字,而 C99 已经有了完美的 int32_t
。还不清楚他们为什么要施加这种限制。也许是因为 MSVC 错过了优化错误?或者可能通过禁止某些 "weird stuff".
gcc 和 clang 不会将 unsigned long long
作为位域类型发出警告,即使在 32 位模式和 -Wall -Wextra -Wpedantic
下,在 C 或 C++ 模式下也是如此。我认为 ISO C 或 ISO C++ 没有问题。
此外,Should use of bit-fields of type int be discouraged? 指出不应将普通的 int
作为位域类型,因为签名是实现定义的。并且 ISO C++ 标准讨论了从 char
到 long long
.
我认为您的 MSVC 关于非 int
位域的警告必须来自某种编码标准强制执行包,因为 Godbolt 上的普通 MSVC 即使使用 `-Wall 也不会这样做。
warning C4214: nonstandard extension used: bit field types other than int