让 gcc 将一系列 BYTE 比较转换为 WORD/DWORD/QWORD
Posted
技术标签:
【中文标题】让 gcc 将一系列 BYTE 比较转换为 WORD/DWORD/QWORD【英文标题】:Make gcc turn a sequence of BYTE comparisons into WORD/DWORD/QWORD instead 【发布时间】:2017-02-02 13:29:04 【问题描述】:我目前有以下:
#include <cstdint>
#include <memory>
template<typename T>
bool isZeroed(T const & num)
void const * ptr = std::addressof(num);
uint8_t const * pos = static_cast<uint8_t const *>(ptr);
uint8_t const * const endpos = pos + sizeof(T);
for (;pos < endpos; ++pos)
if (*pos != uint8_t(0))
return false;
return true;
int main(int argc, char * argv[])
return isZeroed(static_cast<uint64_t>(argc));
在 gcc 7 上使用 -O3 生成:
main:
movsx rdi, edi
test dil, dil
mov QWORD PTR [rsp-8], rdi
jne .L9
cmp BYTE PTR [rsp-7], 0
jne .L9
cmp BYTE PTR [rsp-6], 0
jne .L9
cmp BYTE PTR [rsp-5], 0
jne .L9
cmp BYTE PTR [rsp-4], 0
jne .L9
cmp BYTE PTR [rsp-3], 0
jne .L9
cmp BYTE PTR [rsp-2], 0
jne .L9
cmp BYTE PTR [rsp-1], 0
sete al
.L2:
movzx eax, al
ret
.L9:
xor eax, eax
jmp .L2
见https://godbolt.org/g/HWB3is
在我的脑海中,我认为应该可以将这些 BYTE 比较折叠成一次占用更多字节的比较,例如 WORD/DWORD/QWORD。
有人知道我在代码中做了什么阻止优化器这样做,或者这在 gcc 中是不可能的吗?
【问题讨论】:
我认为编译器忘记了所有字节都是有效的,因此采取措施不访问额外的内存,除非是实际需要的。如果pos[0]
不为零,则源代码不会访问pos[1-7]
,并且编译器会确保该行为。 PS:int
通常不是 64 位的,但可能在您的环境中。无论如何,32 位的代码也是类似的。
所以我需要故意访问更大块的内存,因为编译器也在保留内存访问模式。
是的,编译器正在做你告诉它做的事情,你想让它做一些不同的事情,告诉它......
这不是UB吗?通过不兼容类型的指针专门访问对象(例如*pos
)。为了不那么标准,结构可以有填充,并且填充不需要为零。
@MargaretBloom 这可能不是未定义的行为,因为允许通过unsigned char
指针访问任何类型的对象,而uint8_t
几乎必须是unsigned char
或不存在。示例中使用的类型 uint64_t
不能有任何填充位,但一般来说,给定的类型,而不仅仅是结构,是否有填充是实现定义的。
【参考方案1】:
考虑到 Jester 的评论,我可以执行以下操作。
#include <cstdint>
#include <memory>
template<typename CmpSizeType>
bool isZeroed(void const * & pos, size_t & bytesLeft)
while (bytesLeft >= sizeof(CmpSizeType))
CmpSizeType const * posOfSize = static_cast<CmpSizeType const *>(pos);
if ( *posOfSize != CmpSizeType(0)) return false;
pos = posOfSize + 1;
bytesLeft -= sizeof(CmpSizeType);
return true;
template<typename T>
bool isZeroed(T const & num)
size_t bytesLeft = sizeof(T);
void const * pos = std::addressof(num);
if(!isZeroed<uint64_t>(pos,bytesLeft)) return false;
if(!isZeroed<uint32_t>(pos,bytesLeft)) return false;
if(!isZeroed<uint8_t>(pos,bytesLeft)) return false;
return true;
struct T
int8_t a1,b1,c1;
int16_t a2;
int32_t a3,b3,c3,c4;
int64_t a4;
int16_t a5;
;
int main(int argc, char * argv[])
return
isZeroed(T 0,0,0,0,argc,0,0,0,0,0 ) &&
isZeroed(static_cast<int8_t>(argc));
改为提供 QWORD 比较:
main:
pxor xmm0, xmm0
movaps XMMWORD PTR [rsp-56], xmm0
mov DWORD PTR [rsp-48], edi
cmp QWORD PTR [rsp-48], 0
movaps XMMWORD PTR [rsp-40], xmm0
jne .L6
cmp QWORD PTR [rsp-40], 0
jne .L6
cmp QWORD PTR [rsp-32], 0
jne .L6
xor eax, eax
test dil, dil
sete al
ret
.L6:
xor eax, eax
ret
【讨论】:
GCC 在这里很愚蠢,明确地将零与零进行比较。 Clang 可以更好地编译它 - 但在某种程度上违背了测试的目的。 您的代码无法处理模板参数T
的对齐方式可能不如uint64_t
或uint32_t
严格的情况。它也不处理 T 可能有填充的情况。编译器可能会生成代码,其中 T 0,0,0,0,argc,0,0,0,0,0
创建的对象的第四个字节(以及许多其他字节)未初始化。【参考方案2】:
当您告诉编译器查看 BYTE 大小的块时,它会这样做。如果您希望它查看 DWORD 或 QWORD 大小的块,您需要要求它这样做。类似于以下内容:
template<typename T>
bool isZeroed(T const & num)
void const * ptr = std::addressof(num);
uint8_t const * pos = static_cast<uint8_t const *>(ptr);
uint8_t const * const endpos = pos + sizeof(T);
for (;pos < endpos; pos += sizeof(uint64_t))
if (*reinterpret_cast<const uint64_t*>(pos) != 0)
return false;
for (;pos < endpos; ++pos)
if (*pos != uint8_t(0))
return false;
return true;
我们首先循环比较 QWORD 大小的块与零。如果找到任何非零值,则其中一个 BYTE 必须是非零值,因此返回 false
。然后,我们再次循环处理任何剩余的 BYTE 大小的块。
这会产生您想要的代码。实际上,更好的是——编译器知道你正在向模板函数传递一个 QWORD 大小的值,所以它只使用了 TEST
指令:
main:
xor eax, eax
test edi, edi
sete al
ret
如果您使用 GCC 扩展并传递一个 128 位整数值,则该函数将编译为以下内容,它会按预期进行两次 QWORD 比较:
main:
movsx rdi, edi
cmp QWORD PTR [rdi], 0
jne .L3
cmp QWORD PTR [rdi+8], 0
sete al
.L2:
movzx eax, al
ret
.L3:
xor eax, eax
jmp .L2
请注意,这是非常不安全的,这在reinterpret_cast
中应该很明显。我也质疑这个的效用。为所有整数大小编写模板特化——没有那么多,你会得到更好的代码而不影响安全性。或者,如果您正在测试任意内存块,请使用 memcmp
——例如:
template<typename T>
bool isZeroed(T const & num)
void const * ptr = std::addressof(num);
uint8_t temp[sizeof(T)] = ;
return memcmp(temp, ptr, sizeof(T));
【讨论】:
以上是关于让 gcc 将一系列 BYTE 比较转换为 WORD/DWORD/QWORD的主要内容,如果未能解决你的问题,请参考以下文章