gcc:通过显式 memcpy 避免严格别名违规警告
gcc: avoiding strict-aliasing violation warning by explicit memcpy
我有一个 class 需要 64 位内存。为了实现平等,我使用了 reinterpret_cast<uint64_t*>
,但它在 gcc 7.2(但不是 clang 5.0)上导致了这个警告:
$ g++ -O3 -Wall -std=c++17 -g -c example.cpp
example.cpp: In member function ‘bool X::eq_via_cast(X)’:
example.cpp:27:85: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
return *reinterpret_cast<uint64_t*>(this) == *reinterpret_cast<uint64_t*>(&x); ^
根据我的理解,除非您要转换为实际类型或 char*
,否则转换是未定义的行为。例如,加载值时可能存在特定于体系结构的布局限制。这就是我尝试其他方法的原因。
这里是简化版的源代码(link to godbolt):
#include <cstdint>
#include <cstring>
struct Y
{
uint32_t x;
bool operator==(Y y) { return x == y.x; }
};
struct X
{
Y a;
int16_t b;
int16_t c;
uint64_t to_uint64() {
uint64_t result;
std::memcpy(&result, this, sizeof(uint64_t));
return result;
}
bool eq_via_memcpy(X x) {
return to_uint64() == x.to_uint64();
}
bool eq_via_cast(X x) {
return *reinterpret_cast<uint64_t*>(this) == *reinterpret_cast<uint64_t*>(&x);
}
bool eq_via_comparisons(X x) {
return a == x.a && b == x.b && c == x.c;
}
};
static_assert(sizeof(X) == sizeof(uint64_t));
bool via_memcpy(X x1, X x2) {
return x1.eq_via_memcpy(x2);
}
bool via_cast(X x1, X x2) {
return x1.eq_via_cast(x2);
}
bool via_comparisons(X x1, X x2) {
return x1.eq_via_comparisons(x2);
}
通过 memcpy
显式复制数据来避免强制转换可防止出现警告。据我了解,应该也是便携的。
查看汇编器(gcc 7.2 with -std=c++17 -O3
),memcpy 得到了完美优化,而直接比较导致代码效率较低:
via_memcpy(X, X):
cmp rdi, rsi
sete al
ret
via_cast(X, X):
cmp rdi, rsi
sete al
ret
via_comparisons(X, X):
xor eax, eax
cmp esi, edi
je .L7
rep ret
.L7:
sar rdi, 32
sar rsi, 32
cmp edi, esi
sete al
ret
与 clang 5.0 非常相似 (-std=c++17 -O3
):
via_memcpy(X, X): # @via_memcpy(X, X)
cmp rdi, rsi
sete al
ret
via_cast(X, X): # @via_cast(X, X)
cmp rdi, rsi
sete al
ret
via_comparisons(X, X): # @via_comparisons(X, X)
cmp edi, esi
jne .LBB2_1
mov rax, rdi
shr rax, 32
mov rcx, rsi
shr rcx, 32
shl eax, 16
shl ecx, 16
cmp ecx, eax
jne .LBB2_3
shr rdi, 48
shr rsi, 48
shl edi, 16
shl esi, 16
cmp esi, edi
sete al
ret
.LBB2_1:
xor eax, eax
ret
.LBB2_3:
xor eax, eax
ret
从这个实验来看,memcpy
版本似乎是代码性能关键部分的最佳方法。
问题:
- 我的理解是否正确
memcpy
版本是可移植的 C++ 代码?
- 假设编译器能够像本例中那样优化掉
memcpy
调用是否合理?
- 有没有我忽略的更好的方法?
更新:
正如 UKMonkey 指出的那样,memcmp
在进行按位比较时更自然。它还编译为相同的优化版本:
bool eq_via_memcmp(X x) {
return std::memcmp(this, &x, sizeof(*this)) == 0;
}
这里是updated godbolt link。也应该是可移植的(sizeof(*this)
是 64 位),所以我认为这是迄今为止最好的解决方案。
在C++17中,可以使用memcmp in combination with has_unique_object_representations:
bool eq_via_memcmp(X x) {
static_assert(std::has_unique_object_representations_v<X>);
return std::memcmp(this, &x, sizeof(*this)) == 0;
}
编译器应该能够将其优化为一次比较 (godbolt link):
via_memcmp(X, X):
cmp rdi, rsi
sete al
ret
静态断言确保 class X
不包含填充位。否则,比较两个逻辑上等效的对象可能 return 错误,因为填充位的内容可能不同。在那种情况下,在编译时拒绝该代码会更安全。
(注意:据推测,C++20 将添加 std::bit_cast,它可以用作 memcmp
的替代方法。但是,您仍然必须确保不涉及填充同样的道理。)
我有一个 class 需要 64 位内存。为了实现平等,我使用了 reinterpret_cast<uint64_t*>
,但它在 gcc 7.2(但不是 clang 5.0)上导致了这个警告:
$ g++ -O3 -Wall -std=c++17 -g -c example.cpp
example.cpp: In member function ‘bool X::eq_via_cast(X)’:
example.cpp:27:85: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
return *reinterpret_cast<uint64_t*>(this) == *reinterpret_cast<uint64_t*>(&x); ^
根据我的理解,除非您要转换为实际类型或 char*
,否则转换是未定义的行为。例如,加载值时可能存在特定于体系结构的布局限制。这就是我尝试其他方法的原因。
这里是简化版的源代码(link to godbolt):
#include <cstdint>
#include <cstring>
struct Y
{
uint32_t x;
bool operator==(Y y) { return x == y.x; }
};
struct X
{
Y a;
int16_t b;
int16_t c;
uint64_t to_uint64() {
uint64_t result;
std::memcpy(&result, this, sizeof(uint64_t));
return result;
}
bool eq_via_memcpy(X x) {
return to_uint64() == x.to_uint64();
}
bool eq_via_cast(X x) {
return *reinterpret_cast<uint64_t*>(this) == *reinterpret_cast<uint64_t*>(&x);
}
bool eq_via_comparisons(X x) {
return a == x.a && b == x.b && c == x.c;
}
};
static_assert(sizeof(X) == sizeof(uint64_t));
bool via_memcpy(X x1, X x2) {
return x1.eq_via_memcpy(x2);
}
bool via_cast(X x1, X x2) {
return x1.eq_via_cast(x2);
}
bool via_comparisons(X x1, X x2) {
return x1.eq_via_comparisons(x2);
}
通过 memcpy
显式复制数据来避免强制转换可防止出现警告。据我了解,应该也是便携的。
查看汇编器(gcc 7.2 with -std=c++17 -O3
),memcpy 得到了完美优化,而直接比较导致代码效率较低:
via_memcpy(X, X):
cmp rdi, rsi
sete al
ret
via_cast(X, X):
cmp rdi, rsi
sete al
ret
via_comparisons(X, X):
xor eax, eax
cmp esi, edi
je .L7
rep ret
.L7:
sar rdi, 32
sar rsi, 32
cmp edi, esi
sete al
ret
与 clang 5.0 非常相似 (-std=c++17 -O3
):
via_memcpy(X, X): # @via_memcpy(X, X)
cmp rdi, rsi
sete al
ret
via_cast(X, X): # @via_cast(X, X)
cmp rdi, rsi
sete al
ret
via_comparisons(X, X): # @via_comparisons(X, X)
cmp edi, esi
jne .LBB2_1
mov rax, rdi
shr rax, 32
mov rcx, rsi
shr rcx, 32
shl eax, 16
shl ecx, 16
cmp ecx, eax
jne .LBB2_3
shr rdi, 48
shr rsi, 48
shl edi, 16
shl esi, 16
cmp esi, edi
sete al
ret
.LBB2_1:
xor eax, eax
ret
.LBB2_3:
xor eax, eax
ret
从这个实验来看,memcpy
版本似乎是代码性能关键部分的最佳方法。
问题:
- 我的理解是否正确
memcpy
版本是可移植的 C++ 代码? - 假设编译器能够像本例中那样优化掉
memcpy
调用是否合理? - 有没有我忽略的更好的方法?
更新:
正如 UKMonkey 指出的那样,memcmp
在进行按位比较时更自然。它还编译为相同的优化版本:
bool eq_via_memcmp(X x) {
return std::memcmp(this, &x, sizeof(*this)) == 0;
}
这里是updated godbolt link。也应该是可移植的(sizeof(*this)
是 64 位),所以我认为这是迄今为止最好的解决方案。
在C++17中,可以使用memcmp in combination with has_unique_object_representations:
bool eq_via_memcmp(X x) {
static_assert(std::has_unique_object_representations_v<X>);
return std::memcmp(this, &x, sizeof(*this)) == 0;
}
编译器应该能够将其优化为一次比较 (godbolt link):
via_memcmp(X, X):
cmp rdi, rsi
sete al
ret
静态断言确保 class X
不包含填充位。否则,比较两个逻辑上等效的对象可能 return 错误,因为填充位的内容可能不同。在那种情况下,在编译时拒绝该代码会更安全。
(注意:据推测,C++20 将添加 std::bit_cast,它可以用作 memcmp
的替代方法。但是,您仍然必须确保不涉及填充同样的道理。)